@el4cteo/rbx-studio-mcp 0.3.1 → 0.3.6
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 +20 -26
- package/dist/index.js +1 -1
- package/dist/lib/format.js +18 -2
- package/dist/lib/format.js.map +1 -1
- package/dist/tools/api.js +20 -1
- package/dist/tools/api.js.map +1 -1
- package/dist/tools/debug.js +18 -8
- package/dist/tools/debug.js.map +1 -1
- package/dist/tools/discover.js +5 -0
- package/dist/tools/discover.js.map +1 -1
- package/dist/tools/input.js +38 -5
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/instances.js +49 -2
- package/dist/tools/instances.js.map +1 -1
- package/dist/tools/perf.js +9 -8
- package/dist/tools/perf.js.map +1 -1
- package/dist/tools/scripts.js +10 -1
- package/dist/tools/scripts.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/Config.luau +1 -1
- package/plugin/src/Console.luau +301 -117
- package/plugin/src/Mirror.luau +126 -0
- package/plugin/src/Serialize.luau +43 -3
- package/plugin/src/ThemePicker.luau +458 -0
- package/plugin/src/Themes/Aurora.luau +161 -0
- package/plugin/src/Themes/Blueprint.luau +213 -0
- package/plugin/src/Themes/Draw.luau +177 -0
- package/plugin/src/Themes/Lattice.luau +304 -0
- package/plugin/src/Themes/Nebula.luau +182 -0
- package/plugin/src/Themes/Observatory.luau +200 -0
- package/plugin/src/Themes/Orbit.luau +268 -0
- package/plugin/src/Themes/Phosphor.luau +200 -0
- package/plugin/src/Themes/Theme.luau +121 -0
- package/plugin/src/Themes/Void.luau +266 -0
- package/plugin/src/Themes/init.luau +116 -0
- package/plugin/src/Undo.luau +29 -0
- package/plugin/src/Visuals.luau +838 -907
- package/plugin/src/handlers/Device.luau +21 -1
- package/plugin/src/handlers/Discover.luau +75 -1
- package/plugin/src/handlers/Exec.luau +17 -5
- package/plugin/src/handlers/Input.luau +583 -493
- package/plugin/src/handlers/Instances.luau +35 -3
- package/plugin/src/init.server.luau +209 -11
package/plugin/src/Visuals.luau
CHANGED
|
@@ -1,907 +1,838 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--[[
|
|
3
|
-
The console's activity band: a
|
|
4
|
-
|
|
5
|
-
Watching an agent work is mostly waiting, and the log answers "what is it
|
|
6
|
-
doing" precisely while answering "is it still alive" badly -- a stalled
|
|
7
|
-
session and a thinking one produce the same still screen.
|
|
8
|
-
|
|
9
|
-
This began as a full-panel overlay and that was wrong. It sat on top of the
|
|
10
|
-
log, so the thing you actually read was covered and, being slightly
|
|
11
|
-
transparent, softened underneath. Decoration that costs legibility is a bad
|
|
12
|
-
trade however good it looks. So it is a band now: a fixed strip above the
|
|
13
|
-
log, which keeps every line at full contrast and never moves under the
|
|
14
|
-
reader. The log shrinks by the band's height and nothing overlaps.
|
|
15
|
-
|
|
16
|
-
The strip earns its space twice over. The
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
local
|
|
33
|
-
|
|
34
|
-
local
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
--[[
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
]]
|
|
47
|
-
local
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
--
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
--
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
quiet
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
]]
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
runtime.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
--[[
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
runtime.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
--
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
runtime.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
trace.ClipsDescendants = true
|
|
840
|
-
trace.Parent = band
|
|
841
|
-
runtime.trace = trace
|
|
842
|
-
|
|
843
|
-
-- A baseline under the bars, so an empty trace still reads as an instrument
|
|
844
|
-
-- waiting for data rather than as a blank gap.
|
|
845
|
-
local baseline = Instance.new("Frame")
|
|
846
|
-
baseline.AnchorPoint = Vector2.new(0, 1)
|
|
847
|
-
baseline.Position = UDim2.fromScale(0, 1)
|
|
848
|
-
baseline.Size = UDim2.new(1, 0, 0, 1)
|
|
849
|
-
baseline.BackgroundColor3 = palette.dim
|
|
850
|
-
baseline.BackgroundTransparency = 0.75
|
|
851
|
-
baseline.BorderSizePixel = 0
|
|
852
|
-
baseline.Parent = trace
|
|
853
|
-
|
|
854
|
-
--[[
|
|
855
|
-
Built once and moved, and created BEFORE the bars exist so it sits under
|
|
856
|
-
them in draw order -- a highlight drawn over a one-pixel bar would hide the
|
|
857
|
-
very thing it is pointing at.
|
|
858
|
-
]]
|
|
859
|
-
local highlight = Instance.new("Frame")
|
|
860
|
-
highlight.Name = "Highlight"
|
|
861
|
-
highlight.AnchorPoint = Vector2.new(0.5, 1)
|
|
862
|
-
highlight.Position = UDim2.new(1, 0, 1, 0)
|
|
863
|
-
highlight.Size = UDim2.new(0, BAR_STRIDE, 1, 0)
|
|
864
|
-
highlight.BackgroundColor3 = palette.text or Color3.fromRGB(226, 232, 240)
|
|
865
|
-
-- Faint enough not to hide the bar it sits behind, solid enough to be seen
|
|
866
|
-
-- at all. The previous value was chosen for a highlight that never rendered,
|
|
867
|
-
-- so it had never been looked at.
|
|
868
|
-
highlight.BackgroundTransparency = 0.7
|
|
869
|
-
highlight.BorderSizePixel = 0
|
|
870
|
-
highlight.Visible = false
|
|
871
|
-
highlight.Parent = trace
|
|
872
|
-
runtime.highlight = highlight
|
|
873
|
-
|
|
874
|
-
--[[
|
|
875
|
-
The reading sits at the left end of the trace rather than beside the bar it
|
|
876
|
-
describes. Following the pointer would put it off the right edge for the
|
|
877
|
-
newest bars, which are the ones most often asked about, and the left end is
|
|
878
|
-
empty until forty calls have accumulated.
|
|
879
|
-
]]
|
|
880
|
-
local readout = Instance.new("TextLabel")
|
|
881
|
-
readout.Name = "Readout"
|
|
882
|
-
readout.AnchorPoint = Vector2.new(0, 0.5)
|
|
883
|
-
readout.Position = UDim2.new(0, 0, 0.5, 0)
|
|
884
|
-
-- Sized by its text now that it carries a name as well as a number. A fixed
|
|
885
|
-
-- 110px was enough for "18 ms" and truncates anything with a phrase in front
|
|
886
|
-
-- of it, which is the half worth reading.
|
|
887
|
-
readout.AutomaticSize = Enum.AutomaticSize.X
|
|
888
|
-
readout.Size = UDim2.new(0, 0, 0, 14)
|
|
889
|
-
readout.BackgroundColor3 = palette.background or Color3.fromRGB(11, 12, 20)
|
|
890
|
-
readout.BackgroundTransparency = 0.15
|
|
891
|
-
readout.BorderSizePixel = 0
|
|
892
|
-
readout.Font = Enum.Font.Code
|
|
893
|
-
readout.TextSize = 11
|
|
894
|
-
readout.TextXAlignment = Enum.TextXAlignment.Left
|
|
895
|
-
readout.Text = ""
|
|
896
|
-
readout.Visible = false
|
|
897
|
-
readout.ZIndex = 3
|
|
898
|
-
readout.Parent = trace
|
|
899
|
-
runtime.readout = readout
|
|
900
|
-
|
|
901
|
-
local readoutPadding = Instance.new("UIPadding")
|
|
902
|
-
readoutPadding.PaddingLeft = UDim.new(0, 4)
|
|
903
|
-
readoutPadding.PaddingRight = UDim.new(0, 4)
|
|
904
|
-
readoutPadding.Parent = readout
|
|
905
|
-
end
|
|
906
|
-
|
|
907
|
-
return Visuals
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
The console's activity band: a themed cell beside a live latency trace.
|
|
4
|
+
|
|
5
|
+
Watching an agent work is mostly waiting, and the log answers "what is it
|
|
6
|
+
doing" precisely while answering "is it still alive" badly -- a stalled
|
|
7
|
+
session and a thinking one produce the same still screen.
|
|
8
|
+
|
|
9
|
+
This began as a full-panel overlay and that was wrong. It sat on top of the
|
|
10
|
+
log, so the thing you actually read was covered and, being slightly
|
|
11
|
+
transparent, softened underneath. Decoration that costs legibility is a bad
|
|
12
|
+
trade however good it looks. So it is a band now: a fixed strip above the
|
|
13
|
+
log, which keeps every line at full contrast and never moves under the
|
|
14
|
+
reader. The log shrinks by the band's height and nothing overlaps.
|
|
15
|
+
|
|
16
|
+
The strip earns its space twice over. The cell on the left says the session
|
|
17
|
+
is live; the trace beside it is the last forty calls, each drawn as tall as
|
|
18
|
+
it was slow and coloured by whether it worked. That turns "is it alive" and
|
|
19
|
+
"is it healthy" into one glance, which is more than the log gives and more
|
|
20
|
+
than an animation alone would.
|
|
21
|
+
|
|
22
|
+
WHAT THIS FILE OWNS, since it used to own everything: the strip's Instances,
|
|
23
|
+
the pointer handling on the trace, and the simulation -- energy, activity,
|
|
24
|
+
flare, shake, the scale spring, the quiet timer and the blended tint. What
|
|
25
|
+
it does NOT own is any drawing inside the cell. That belongs to whichever
|
|
26
|
+
preset is active (see Themes/), which is handed the simulation each frame
|
|
27
|
+
and paints whatever it likes with it. Eight presets are therefore eight
|
|
28
|
+
painters over one set of physics rather than eight animation systems, and
|
|
29
|
+
only one of them is ever mounted.
|
|
30
|
+
]]
|
|
31
|
+
|
|
32
|
+
local RunService = game:GetService("RunService")
|
|
33
|
+
|
|
34
|
+
local Themes = require(script.Parent.Themes)
|
|
35
|
+
|
|
36
|
+
local Visuals = {}
|
|
37
|
+
|
|
38
|
+
-- How long the session must go without a command before the cell winds down.
|
|
39
|
+
-- Matches the console's idle notice, so the picture and the log agree.
|
|
40
|
+
local QUIET_AFTER = 20
|
|
41
|
+
|
|
42
|
+
--[[
|
|
43
|
+
Spring constants for the scale. Stiff enough that a call reads as a snap
|
|
44
|
+
rather than a swell, damped just under critical so a reply overshoots once
|
|
45
|
+
and settles instead of ringing.
|
|
46
|
+
]]
|
|
47
|
+
local SPRING = 90
|
|
48
|
+
local DAMPING = 14
|
|
49
|
+
|
|
50
|
+
-- How many calls the trace remembers. Forty is about a screen's width of bars
|
|
51
|
+
-- at five pixels each, and about as far back as anyone reads a trend.
|
|
52
|
+
local TRACE = 40
|
|
53
|
+
|
|
54
|
+
-- Bars are scaled against this, so a normal call is a low bar and a slow one is
|
|
55
|
+
-- obvious. Anything past it clamps to full height rather than flattening the
|
|
56
|
+
-- rest of the trace into invisibility.
|
|
57
|
+
local SLOW_MS = 400
|
|
58
|
+
|
|
59
|
+
-- Pixels from one bar to the next. Also the width of a bar's hit target and of
|
|
60
|
+
-- the hover highlight, so pointing between two bars still selects one of them
|
|
61
|
+
-- rather than falling into a dead gap.
|
|
62
|
+
local BAR_STRIDE = 5
|
|
63
|
+
|
|
64
|
+
-- Height of the trace, and so of every bar's hit column. The taller band spends
|
|
65
|
+
-- its extra pixels here: a 19px column was a small thing to aim a pointer at.
|
|
66
|
+
local TRACE_HEIGHT = 26
|
|
67
|
+
|
|
68
|
+
--[[
|
|
69
|
+
The strip's height, and the geometry around the cell.
|
|
70
|
+
|
|
71
|
+
The cell was originally as tall as the band and read as looming; it crowded
|
|
72
|
+
the caption and made the strip feel like a panel. It is an indicator, not the
|
|
73
|
+
subject, so it sits small with air around it.
|
|
74
|
+
|
|
75
|
+
INSET and GUTTER both grew after the cell stopped being a wireframe and
|
|
76
|
+
started being eight different things. At six pixels from the panel edge the
|
|
77
|
+
cell was pressed into the corner, which reads as cramped for a prism and
|
|
78
|
+
simply wrong for a starfield -- a sky needs a horizon around it, not a
|
|
79
|
+
border. The band grew with them so the extra margin comes out of empty space
|
|
80
|
+
rather than out of the trace, whose bars are what people try to point at.
|
|
81
|
+
]]
|
|
82
|
+
Visuals.BAND_HEIGHT = 58
|
|
83
|
+
local CELL = 40
|
|
84
|
+
local INSET = 12
|
|
85
|
+
local GUTTER = 22
|
|
86
|
+
|
|
87
|
+
type Bar = {
|
|
88
|
+
frame: Frame,
|
|
89
|
+
--[[
|
|
90
|
+
An invisible column over the bar, full height of the trace, wide enough to
|
|
91
|
+
hit. The bar itself is three pixels wide and usually one pixel tall -- a
|
|
92
|
+
fast call is a stub on the baseline -- so hovering the bar is not something
|
|
93
|
+
a person can reliably do. The hit target is the whole column above it, so
|
|
94
|
+
pointing anywhere near a bar selects it.
|
|
95
|
+
]]
|
|
96
|
+
hit: TextButton,
|
|
97
|
+
milliseconds: number,
|
|
98
|
+
ok: boolean,
|
|
99
|
+
--[[
|
|
100
|
+
What ran, in the same words the log uses.
|
|
101
|
+
|
|
102
|
+
The trace plotted durations with nothing to attach them to, so a tall bar
|
|
103
|
+
raised the question it could not answer: forty numbers and no way to tell
|
|
104
|
+
which of them was the script edit. Carried per bar rather than looked up,
|
|
105
|
+
because the bar outlives whatever produced it.
|
|
106
|
+
]]
|
|
107
|
+
title: string,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type Runtime = {
|
|
111
|
+
band: Frame?,
|
|
112
|
+
cell: Frame?,
|
|
113
|
+
trace: Frame?,
|
|
114
|
+
divider: Frame?,
|
|
115
|
+
baseline: Frame?,
|
|
116
|
+
highlight: Frame?,
|
|
117
|
+
readout: TextLabel?,
|
|
118
|
+
caption: TextLabel?,
|
|
119
|
+
bars: { Bar },
|
|
120
|
+
connection: RBXScriptConnection?,
|
|
121
|
+
|
|
122
|
+
spin: Vector3,
|
|
123
|
+
angle: Vector3,
|
|
124
|
+
|
|
125
|
+
energy: number,
|
|
126
|
+
shake: number,
|
|
127
|
+
--[[
|
|
128
|
+
The cell's size, as a spring rather than a value.
|
|
129
|
+
|
|
130
|
+
Scale was previously a small function of energy -- a 24% range that
|
|
131
|
+
nobody could see. Driving it as a spring means each event can push it and
|
|
132
|
+
let physics do the rest: a dispatch pulls it in, a reply throws it out
|
|
133
|
+
past its resting size, and it settles on its own.
|
|
134
|
+
]]
|
|
135
|
+
scale: number,
|
|
136
|
+
scaleVelocity: number,
|
|
137
|
+
-- Spikes to 1 the instant a reply lands, gone in a third of a second.
|
|
138
|
+
flare: number,
|
|
139
|
+
-- Seconds since anything last ran. Drives the wind-down.
|
|
140
|
+
quiet: number,
|
|
141
|
+
tint: Color3,
|
|
142
|
+
targetTint: Color3,
|
|
143
|
+
baseTint: Color3,
|
|
144
|
+
activeTint: Color3,
|
|
145
|
+
activity: number,
|
|
146
|
+
lastTitle: string?,
|
|
147
|
+
-- Whether the active preset has been given its Instances yet.
|
|
148
|
+
mounted: boolean,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
local runtime: Runtime = {
|
|
152
|
+
band = nil,
|
|
153
|
+
cell = nil,
|
|
154
|
+
trace = nil,
|
|
155
|
+
divider = nil,
|
|
156
|
+
baseline = nil,
|
|
157
|
+
highlight = nil,
|
|
158
|
+
readout = nil,
|
|
159
|
+
caption = nil,
|
|
160
|
+
bars = {},
|
|
161
|
+
connection = nil,
|
|
162
|
+
spin = Vector3.new(0.18, 0.27, 0.11),
|
|
163
|
+
angle = Vector3.zero,
|
|
164
|
+
energy = 0,
|
|
165
|
+
shake = 0,
|
|
166
|
+
scale = 1,
|
|
167
|
+
scaleVelocity = 0,
|
|
168
|
+
flare = 0,
|
|
169
|
+
quiet = 0,
|
|
170
|
+
tint = Color3.fromRGB(167, 139, 250),
|
|
171
|
+
targetTint = Color3.fromRGB(167, 139, 250),
|
|
172
|
+
baseTint = Color3.fromRGB(167, 139, 250),
|
|
173
|
+
activeTint = Color3.fromRGB(167, 139, 250),
|
|
174
|
+
activity = 0,
|
|
175
|
+
lastTitle = nil,
|
|
176
|
+
mounted = false,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
--[[
|
|
180
|
+
The simulation, packaged for whichever preset is drawing it.
|
|
181
|
+
|
|
182
|
+
Rebuilt every frame rather than mutated in place. A preset holding onto the
|
|
183
|
+
table between frames would see values change under it, and the one rule that
|
|
184
|
+
keeps eight renderers honest is that they read this and draw -- they do not
|
|
185
|
+
own any of it.
|
|
186
|
+
]]
|
|
187
|
+
local function context(): Themes.Context
|
|
188
|
+
return {
|
|
189
|
+
palette = Themes.palette(),
|
|
190
|
+
tint = runtime.tint,
|
|
191
|
+
energy = runtime.energy,
|
|
192
|
+
activity = runtime.activity,
|
|
193
|
+
flare = runtime.flare,
|
|
194
|
+
shake = runtime.shake,
|
|
195
|
+
scale = runtime.scale,
|
|
196
|
+
quiet = runtime.quiet,
|
|
197
|
+
angle = runtime.angle,
|
|
198
|
+
clock = os.clock(),
|
|
199
|
+
size = CELL,
|
|
200
|
+
centre = Vector2.new(CELL / 2, CELL / 2),
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
local function step(delta: number)
|
|
205
|
+
local band = runtime.band
|
|
206
|
+
local cell = runtime.cell
|
|
207
|
+
if band == nil or cell == nil or not band.Visible then
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
runtime.energy = math.max(0, runtime.energy - delta * 0.9)
|
|
212
|
+
runtime.shake = math.max(0, runtime.shake - delta * 2.4)
|
|
213
|
+
--[[
|
|
214
|
+
Slower than it was. The cell is meant to climb as the session works, and
|
|
215
|
+
at the old rates a call had to arrive every three seconds just to hold the
|
|
216
|
+
middle of the range -- so the top of it was, in practice, unreachable. A
|
|
217
|
+
burst of four calls now gets there, and it unwinds over about ten seconds
|
|
218
|
+
afterwards.
|
|
219
|
+
]]
|
|
220
|
+
runtime.activity = math.max(0, runtime.activity - delta * 0.28)
|
|
221
|
+
runtime.flare = math.max(0, runtime.flare - delta * 3)
|
|
222
|
+
runtime.quiet += delta
|
|
223
|
+
--[[
|
|
224
|
+
Two colours, blended by how recently something happened.
|
|
225
|
+
|
|
226
|
+
`baseTint` is the connection state, which is what the panel should read
|
|
227
|
+
as when nothing is going on. `activeTint` is the kind of work last done
|
|
228
|
+
-- reading, writing, running, debugging -- and it takes over while that
|
|
229
|
+
work is fresh, then fades back. So the strip is not one fixed colour: it
|
|
230
|
+
leans toward whatever the session is actually doing and returns to
|
|
231
|
+
resting on its own.
|
|
232
|
+
]]
|
|
233
|
+
local blend = math.clamp(runtime.energy, 0, 1)
|
|
234
|
+
runtime.targetTint = runtime.baseTint:Lerp(runtime.activeTint, blend)
|
|
235
|
+
runtime.tint = runtime.tint:Lerp(runtime.targetTint, math.min(1, delta * 5))
|
|
236
|
+
|
|
237
|
+
-- Turning faster while busy makes the rate itself readable: a glance says
|
|
238
|
+
-- whether anything is happening without reading a word.
|
|
239
|
+
--
|
|
240
|
+
-- And nearly stopping once the session has gone quiet, for the same reason in
|
|
241
|
+
-- reverse: a strip that keeps moving at its working rate hours after the
|
|
242
|
+
-- last command claims an activity that is not happening.
|
|
243
|
+
local pace = if runtime.quiet > QUIET_AFTER then 0.25 else 1
|
|
244
|
+
runtime.angle += runtime.spin * delta * (1 + runtime.energy * 2.2) * pace
|
|
245
|
+
|
|
246
|
+
--[[
|
|
247
|
+
Where the cell wants to be, before the spring gets a say.
|
|
248
|
+
|
|
249
|
+
Three things move it and they are deliberately ordered. Energy -- how
|
|
250
|
+
recently work happened -- swells it while a session is busy. The breathe
|
|
251
|
+
is a slow sine that only survives while energy is near zero, so an idle
|
|
252
|
+
panel is visibly alive without a resting animation competing with the
|
|
253
|
+
reaction to a real call. And past QUIET_AFTER seconds of nothing, it draws
|
|
254
|
+
in and stays there: the picture of a session that has stopped.
|
|
255
|
+
]]
|
|
256
|
+
local calm = 1 - math.clamp(runtime.energy, 0, 1)
|
|
257
|
+
local breathe = math.sin(os.clock() * 0.9) * 0.04 * calm
|
|
258
|
+
local target = 1 + math.clamp(runtime.energy, 0, 1.6) * 0.18 + breathe
|
|
259
|
+
if runtime.quiet > QUIET_AFTER then
|
|
260
|
+
target = 0.82
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
--[[
|
|
264
|
+
A damped spring rather than a lerp, because a lerp cannot overshoot and
|
|
265
|
+
overshoot is the whole point: a reply that pushes the cell past its
|
|
266
|
+
resting size and lets it fall back reads as a thing being struck, where
|
|
267
|
+
easing toward a value reads as a slider being dragged.
|
|
268
|
+
|
|
269
|
+
Integrated semi-implicitly -- velocity first, then position from the new
|
|
270
|
+
velocity -- which stays stable at the frame times Studio actually hands us
|
|
271
|
+
rather than only at small ones.
|
|
272
|
+
]]
|
|
273
|
+
--[[
|
|
274
|
+
Substepped rather than truncated.
|
|
275
|
+
|
|
276
|
+
`math.min(delta, 1/30)` kept the integration stable, but it also THREW
|
|
277
|
+
AWAY the rest of a long frame: at 20fps a third of every frame's motion
|
|
278
|
+
simply did not happen, so the spring ran slower in wall-clock time
|
|
279
|
+
exactly when Studio was busiest -- which is during a tool call, which is
|
|
280
|
+
when the spring is reacting to something. Stepping the leftover instead
|
|
281
|
+
keeps the reaction the same length whatever the frame rate, and the cap
|
|
282
|
+
on the number of steps stops a two-second hitch from paying it all back
|
|
283
|
+
in one frame.
|
|
284
|
+
]]
|
|
285
|
+
local remaining = math.min(delta, 0.25)
|
|
286
|
+
for _ = 1, 8 do
|
|
287
|
+
if remaining <= 0 then
|
|
288
|
+
break
|
|
289
|
+
end
|
|
290
|
+
local integration = math.min(remaining, 1 / 60)
|
|
291
|
+
remaining -= integration
|
|
292
|
+
|
|
293
|
+
runtime.scaleVelocity += (target - runtime.scale) * SPRING * integration
|
|
294
|
+
runtime.scaleVelocity -= runtime.scaleVelocity * DAMPING * integration
|
|
295
|
+
runtime.scale += runtime.scaleVelocity * integration
|
|
296
|
+
|
|
297
|
+
--[[
|
|
298
|
+
Clamping the position without killing the velocity is what made a
|
|
299
|
+
reply read as a jolt rather than a bounce. The reply impulse is
|
|
300
|
+
`scaleVelocity += 9`, which crosses the whole 0.55..1.7 range in
|
|
301
|
+
about two frames, so the scale pinned itself against the ceiling
|
|
302
|
+
while still carrying all that speed -- and then sat there, motionless
|
|
303
|
+
but not at rest, until the spring had spent the stored velocity. The
|
|
304
|
+
eye reads that as a snap, a stall, and a second snap.
|
|
305
|
+
|
|
306
|
+
Zeroing the component pushing into the rail turns it into a stop.
|
|
307
|
+
]]
|
|
308
|
+
if runtime.scale <= 0.55 then
|
|
309
|
+
runtime.scale = 0.55
|
|
310
|
+
runtime.scaleVelocity = math.max(runtime.scaleVelocity, 0)
|
|
311
|
+
elseif runtime.scale >= 1.7 then
|
|
312
|
+
runtime.scale = 1.7
|
|
313
|
+
runtime.scaleVelocity = math.min(runtime.scaleVelocity, 0)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
--[[
|
|
318
|
+
Handed off. A preset that throws must not take the console's status
|
|
319
|
+
display down with it, so a failure here disables the strip and says so
|
|
320
|
+
once rather than erroring sixty times a second.
|
|
321
|
+
]]
|
|
322
|
+
local ok, err = pcall(Themes.active().paint, context(), delta)
|
|
323
|
+
if not ok then
|
|
324
|
+
band.Visible = false
|
|
325
|
+
warn(string.format("[rbx-studio] theme %q failed to paint: %s", Themes.activeId(), tostring(err)))
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
--[[
|
|
330
|
+
Puts the highlight behind one bar and prints its timing.
|
|
331
|
+
|
|
332
|
+
The trace is only legible if you already know what it plots, and nothing on
|
|
333
|
+
screen said so. Hovering answers it directly: the column lights up, and the
|
|
334
|
+
number that made that bar its height appears in words. One reading at a time,
|
|
335
|
+
so a single highlight and a single label are moved around rather than one of
|
|
336
|
+
each being built per bar.
|
|
337
|
+
]]
|
|
338
|
+
local function showReading(entry: Bar)
|
|
339
|
+
local highlight = runtime.highlight
|
|
340
|
+
local readout = runtime.readout
|
|
341
|
+
if highlight == nil or readout == nil then
|
|
342
|
+
return
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
--[[
|
|
346
|
+
The Y here has to be 1, and was 0.
|
|
347
|
+
|
|
348
|
+
The highlight is anchored to its own bottom edge, so a Y scale of 0 put
|
|
349
|
+
that edge on the trace's top line and drew the whole 26px column above it
|
|
350
|
+
-- outside a frame that clips its descendants. The hover was therefore
|
|
351
|
+
never visible to anyone: the hit targets fired, the readout appeared, and
|
|
352
|
+
the highlight they were meant to explain was off screen every time.
|
|
353
|
+
]]
|
|
354
|
+
highlight.Position = UDim2.new(entry.hit.Position.X.Scale, entry.hit.Position.X.Offset, 1, 0)
|
|
355
|
+
highlight.Visible = true
|
|
356
|
+
|
|
357
|
+
--[[
|
|
358
|
+
Named, not just timed.
|
|
359
|
+
|
|
360
|
+
A duration with nothing attached to it raises the question it cannot
|
|
361
|
+
answer -- forty bars, one of them tall, and no way to tell whether that
|
|
362
|
+
was a script edit or a screenshot. The title is the same phrase the log
|
|
363
|
+
row uses, so pointing at a bar and reading the log agree with each other.
|
|
364
|
+
]]
|
|
365
|
+
local palette = Themes.palette()
|
|
366
|
+
readout.Text = if entry.ok
|
|
367
|
+
then string.format("%s: %d ms", entry.title, math.round(entry.milliseconds))
|
|
368
|
+
else string.format("%s: failed after %d ms", entry.title, math.round(entry.milliseconds))
|
|
369
|
+
readout.TextColor3 = if entry.ok then palette.text else palette.red
|
|
370
|
+
readout.Visible = true
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
local function clearReading()
|
|
374
|
+
if runtime.highlight then
|
|
375
|
+
runtime.highlight.Visible = false
|
|
376
|
+
end
|
|
377
|
+
if runtime.readout then
|
|
378
|
+
runtime.readout.Visible = false
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
--[[
|
|
383
|
+
Bars, for a preset that does not want to draw its own history.
|
|
384
|
+
|
|
385
|
+
Height is duration and colour is outcome, so a slow call and a failed one are
|
|
386
|
+
distinguishable at a glance -- which is the pair of questions that actually
|
|
387
|
+
get asked of a log this size.
|
|
388
|
+
]]
|
|
389
|
+
local function defaultSlot(slot: Themes.Slot, ctx: Themes.Context)
|
|
390
|
+
local frame = slot.frame
|
|
391
|
+
frame.AnchorPoint = Vector2.new(0.5, 1)
|
|
392
|
+
frame.Size = UDim2.new(0, 3, math.max(slot.weight, 0.06), 0)
|
|
393
|
+
frame.Position = UDim2.new(frame.Position.X.Scale, frame.Position.X.Offset, 1, 0)
|
|
394
|
+
frame.BackgroundColor3 = if slot.ok then ctx.palette.violet else ctx.palette.red
|
|
395
|
+
frame.BackgroundTransparency = 0.15 + slot.age * 0.6
|
|
396
|
+
frame.Rotation = 0
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
--[[
|
|
400
|
+
Re-places every bar and hands each to the active preset to draw.
|
|
401
|
+
|
|
402
|
+
Laid out right to left so the newest bar is always at the same edge and the
|
|
403
|
+
trace reads as scrolling rather than reshuffling. The horizontal position is
|
|
404
|
+
set here and the preset is expected to keep it -- everything else about the
|
|
405
|
+
bar is the preset's to decide.
|
|
406
|
+
]]
|
|
407
|
+
local function relayout()
|
|
408
|
+
local ctx = context()
|
|
409
|
+
local painter = Themes.active().paintSlot or defaultSlot
|
|
410
|
+
local total = #runtime.bars
|
|
411
|
+
|
|
412
|
+
for index, item in runtime.bars do
|
|
413
|
+
local fromRight = total - index
|
|
414
|
+
local offset = -(fromRight + 1) * BAR_STRIDE + BAR_STRIDE / 2
|
|
415
|
+
|
|
416
|
+
item.frame.Position = UDim2.new(1, offset, 1, 0)
|
|
417
|
+
item.hit.Position = UDim2.new(1, offset, 1, 0)
|
|
418
|
+
|
|
419
|
+
local slot: Themes.Slot = {
|
|
420
|
+
frame = item.frame,
|
|
421
|
+
milliseconds = item.milliseconds,
|
|
422
|
+
ok = item.ok,
|
|
423
|
+
title = item.title,
|
|
424
|
+
-- 0 for the newest, rising toward 1 for the oldest still shown.
|
|
425
|
+
age = if TRACE > 1 then fromRight / (TRACE - 1) else 0,
|
|
426
|
+
weight = math.clamp(item.milliseconds / SLOW_MS, 0.06, 1),
|
|
427
|
+
}
|
|
428
|
+
local ok, err = pcall(painter, slot, ctx)
|
|
429
|
+
if not ok then
|
|
430
|
+
-- One bad slot must not leave the other thirty-nine unplaced.
|
|
431
|
+
defaultSlot(slot, ctx)
|
|
432
|
+
warn(string.format("[rbx-studio] theme %q failed on a slot: %s", Themes.activeId(), tostring(err)))
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
--[[
|
|
438
|
+
Records one finished call and re-draws the trace.
|
|
439
|
+
]]
|
|
440
|
+
function Visuals.recordCall(milliseconds: number, ok: boolean, title: string)
|
|
441
|
+
runtime.energy = math.min(1.6, runtime.energy + (if ok then 0.3 else 0.8))
|
|
442
|
+
runtime.activity = math.min(1, runtime.activity + 0.34)
|
|
443
|
+
runtime.quiet = 0
|
|
444
|
+
runtime.flare = 1
|
|
445
|
+
|
|
446
|
+
--[[
|
|
447
|
+
A shove rather than a new target.
|
|
448
|
+
|
|
449
|
+
Setting the scale outright would snap; giving the spring velocity lets it
|
|
450
|
+
carry past its resting size and fall back, which is the difference between
|
|
451
|
+
a value changing and something being struck. A failure shoves the other
|
|
452
|
+
way and knocks the axis with it.
|
|
453
|
+
]]
|
|
454
|
+
if ok then
|
|
455
|
+
--[[
|
|
456
|
+
Sized to peak just under the clamp rather than against it.
|
|
457
|
+
|
|
458
|
+
At +9 the shove carried the scale into the 1.7 ceiling within about a
|
|
459
|
+
tenth of a second, so what should have been an arc became a rise, a
|
|
460
|
+
flat spot, and a drop -- and a burst of calls, each adding another 9
|
|
461
|
+
to a velocity that was already railed, held it pinned there for as
|
|
462
|
+
long as the burst lasted. +6 tops out around 1.55, which reads as the
|
|
463
|
+
same strike and stays on the curve. The accumulated case is capped
|
|
464
|
+
for the same reason: several calls at once should look busy, not
|
|
465
|
+
stuck.
|
|
466
|
+
]]
|
|
467
|
+
runtime.scaleVelocity = math.min(runtime.scaleVelocity + 6, 8)
|
|
468
|
+
else
|
|
469
|
+
runtime.scale = 0.8
|
|
470
|
+
runtime.scaleVelocity = -2
|
|
471
|
+
runtime.shake = 1
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
local trace = runtime.trace
|
|
475
|
+
if trace == nil then
|
|
476
|
+
return
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
local bar = Instance.new("Frame")
|
|
480
|
+
bar.AnchorPoint = Vector2.new(0.5, 1)
|
|
481
|
+
bar.BorderSizePixel = 0
|
|
482
|
+
bar.Parent = trace
|
|
483
|
+
-- Presets draw points as well as bars, and a square point is not a point.
|
|
484
|
+
local corner = Instance.new("UICorner")
|
|
485
|
+
corner.CornerRadius = UDim.new(0, 1)
|
|
486
|
+
corner.Parent = bar
|
|
487
|
+
|
|
488
|
+
-- A TextButton rather than a Frame: buttons take mouse events reliably in a
|
|
489
|
+
-- plugin widget, and with no text and no background it is purely a target.
|
|
490
|
+
local hit = Instance.new("TextButton")
|
|
491
|
+
hit.AnchorPoint = Vector2.new(0.5, 1)
|
|
492
|
+
hit.BackgroundTransparency = 1
|
|
493
|
+
hit.Text = ""
|
|
494
|
+
hit.AutoButtonColor = false
|
|
495
|
+
hit.BorderSizePixel = 0
|
|
496
|
+
hit.Size = UDim2.new(0, BAR_STRIDE, 1, 0)
|
|
497
|
+
hit.Parent = trace
|
|
498
|
+
|
|
499
|
+
local entry: Bar = {
|
|
500
|
+
frame = bar,
|
|
501
|
+
hit = hit,
|
|
502
|
+
milliseconds = milliseconds,
|
|
503
|
+
ok = ok,
|
|
504
|
+
title = if title ~= "" then title else "call",
|
|
505
|
+
}
|
|
506
|
+
table.insert(runtime.bars, entry)
|
|
507
|
+
|
|
508
|
+
hit.MouseEnter:Connect(function()
|
|
509
|
+
showReading(entry)
|
|
510
|
+
end)
|
|
511
|
+
hit.MouseLeave:Connect(function()
|
|
512
|
+
clearReading()
|
|
513
|
+
end)
|
|
514
|
+
|
|
515
|
+
while #runtime.bars > TRACE do
|
|
516
|
+
local oldest = runtime.bars[1]
|
|
517
|
+
oldest.frame:Destroy()
|
|
518
|
+
oldest.hit:Destroy()
|
|
519
|
+
table.remove(runtime.bars, 1)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
relayout()
|
|
523
|
+
|
|
524
|
+
-- The bars have all moved, so a reading still on screen now names the wrong
|
|
525
|
+
-- one. Cheaper and more honest to drop it than to work out which bar the
|
|
526
|
+
-- pointer has ended up over.
|
|
527
|
+
clearReading()
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
--[[
|
|
531
|
+
The resting colour, which is the connection state.
|
|
532
|
+
]]
|
|
533
|
+
function Visuals.setTint(color: Color3)
|
|
534
|
+
runtime.baseTint = color
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
--[[
|
|
538
|
+
The colour and pace of the work now running.
|
|
539
|
+
|
|
540
|
+
Both are set from the same call because they describe the same thing: what
|
|
541
|
+
kind of command this is. Reads are quick and cool, writes are slower and
|
|
542
|
+
warm, so the strip's colour and its rate agree with each other instead of
|
|
543
|
+
moving independently.
|
|
544
|
+
]]
|
|
545
|
+
function Visuals.setKind(color: Color3, urgency: number)
|
|
546
|
+
runtime.activeTint = color
|
|
547
|
+
runtime.energy = math.min(1.6, math.max(runtime.energy, urgency))
|
|
548
|
+
runtime.quiet = 0
|
|
549
|
+
--[[
|
|
550
|
+
Drawn in as the command goes out, so the pair reads as one gesture: the
|
|
551
|
+
cell contracts while the request is in flight and springs open when the
|
|
552
|
+
answer arrives. On a fast call the two are almost one motion, which is
|
|
553
|
+
itself the report -- a slow call visibly holds its breath.
|
|
554
|
+
]]
|
|
555
|
+
runtime.scale = math.min(runtime.scale, 0.85)
|
|
556
|
+
runtime.scaleVelocity = math.min(runtime.scaleVelocity, 0)
|
|
557
|
+
-- Re-aimed per kind so successive commands of different types visibly
|
|
558
|
+
-- change the axis rather than continuing the same turn.
|
|
559
|
+
runtime.spin = Vector3.new(
|
|
560
|
+
0.12 + math.random() * 0.16,
|
|
561
|
+
0.18 + math.random() * 0.22,
|
|
562
|
+
0.06 + math.random() * 0.12
|
|
563
|
+
)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
--[[
|
|
567
|
+
What is running, in the same words the log uses. The motion says something is
|
|
568
|
+
happening; this says what, and neither answers the other's question.
|
|
569
|
+
]]
|
|
570
|
+
function Visuals.setCaption(title: string)
|
|
571
|
+
runtime.lastTitle = title
|
|
572
|
+
if runtime.caption then
|
|
573
|
+
runtime.caption.Text = title
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
--[[
|
|
578
|
+
Falls back to the last thing that ran once a command finishes.
|
|
579
|
+
|
|
580
|
+
The footer already reports totals, so repeating "idle" here would say the
|
|
581
|
+
same word twice on one screen. What has just happened is more useful and is
|
|
582
|
+
not shown anywhere else.
|
|
583
|
+
]]
|
|
584
|
+
function Visuals.setIdle()
|
|
585
|
+
if runtime.caption == nil then
|
|
586
|
+
return
|
|
587
|
+
end
|
|
588
|
+
runtime.caption.Text = if runtime.lastTitle ~= nil
|
|
589
|
+
then "last: " .. runtime.lastTitle
|
|
590
|
+
else "waiting for a command"
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
--[[
|
|
594
|
+
Winds the cell down, because the session has stopped rather than paused.
|
|
595
|
+
|
|
596
|
+
The strip already decays on its own, but decay bottoms out at "idle and
|
|
597
|
+
moving", which looks the same after twenty seconds as after two hours. This
|
|
598
|
+
is the console telling the picture what it has just told the log, so the two
|
|
599
|
+
agree.
|
|
600
|
+
]]
|
|
601
|
+
function Visuals.setQuiet()
|
|
602
|
+
runtime.quiet = QUIET_AFTER + 1
|
|
603
|
+
runtime.activity = 0
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
--[[
|
|
607
|
+
Empties the trace.
|
|
608
|
+
|
|
609
|
+
Paired with the console's clear button. The bars used to survive it while the
|
|
610
|
+
footer counters reset, so "clear" wiped the log, wiped the statistics, and
|
|
611
|
+
left forty timings from the session it had just erased sitting on screen.
|
|
612
|
+
]]
|
|
613
|
+
function Visuals.clearTrace()
|
|
614
|
+
for _, entry in runtime.bars do
|
|
615
|
+
entry.frame:Destroy()
|
|
616
|
+
entry.hit:Destroy()
|
|
617
|
+
end
|
|
618
|
+
table.clear(runtime.bars)
|
|
619
|
+
clearReading()
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
function Visuals.setVisible(visible: boolean)
|
|
623
|
+
local band = runtime.band
|
|
624
|
+
if band == nil then
|
|
625
|
+
return
|
|
626
|
+
end
|
|
627
|
+
band.Visible = visible
|
|
628
|
+
|
|
629
|
+
-- Connected only while on screen: a hidden strip that keeps projecting
|
|
630
|
+
-- geometry every frame is a battery complaint waiting to happen.
|
|
631
|
+
if visible and runtime.connection == nil then
|
|
632
|
+
runtime.connection = RunService.Heartbeat:Connect(step)
|
|
633
|
+
elseif not visible and runtime.connection ~= nil then
|
|
634
|
+
runtime.connection:Disconnect()
|
|
635
|
+
runtime.connection = nil
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
function Visuals.isVisible(): boolean
|
|
640
|
+
local band = runtime.band
|
|
641
|
+
return band ~= nil and band.Visible
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
--[[
|
|
645
|
+
Swaps the cell's contents and recolours the strip's own chrome.
|
|
646
|
+
|
|
647
|
+
The order matters. The outgoing preset is unmounted before the cell is
|
|
648
|
+
emptied, so a preset that keeps references cannot be handed destroyed
|
|
649
|
+
Instances; the cell is then cleared outright rather than trusted to be
|
|
650
|
+
clean, because a preset that errored midway through `mount` will have left
|
|
651
|
+
something behind.
|
|
652
|
+
]]
|
|
653
|
+
function Visuals.applyTheme()
|
|
654
|
+
local cell = runtime.cell
|
|
655
|
+
local band = runtime.band
|
|
656
|
+
if cell == nil or band == nil then
|
|
657
|
+
return
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
if runtime.mounted then
|
|
661
|
+
pcall(function()
|
|
662
|
+
Themes.active().unmount()
|
|
663
|
+
end)
|
|
664
|
+
end
|
|
665
|
+
for _, child in cell:GetChildren() do
|
|
666
|
+
child:Destroy()
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
local palette = Themes.palette()
|
|
670
|
+
band.BackgroundColor3 = palette.surface
|
|
671
|
+
if runtime.divider then
|
|
672
|
+
(runtime.divider :: Frame).BackgroundColor3 = palette.dim
|
|
673
|
+
end
|
|
674
|
+
if runtime.baseline then
|
|
675
|
+
(runtime.baseline :: Frame).BackgroundColor3 = palette.dim
|
|
676
|
+
end
|
|
677
|
+
if runtime.highlight then
|
|
678
|
+
(runtime.highlight :: Frame).BackgroundColor3 = palette.text
|
|
679
|
+
end
|
|
680
|
+
if runtime.caption then
|
|
681
|
+
(runtime.caption :: TextLabel).TextColor3 = palette.text
|
|
682
|
+
end
|
|
683
|
+
if runtime.readout then
|
|
684
|
+
(runtime.readout :: TextLabel).BackgroundColor3 = palette.background
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
local ok, err = pcall(function()
|
|
688
|
+
Themes.active().mount(cell)
|
|
689
|
+
end)
|
|
690
|
+
runtime.mounted = ok
|
|
691
|
+
if not ok then
|
|
692
|
+
warn(string.format("[rbx-studio] theme %q failed to mount: %s", Themes.activeId(), tostring(err)))
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
relayout()
|
|
696
|
+
-- A preset that failed to paint disabled the band; a new one deserves the
|
|
697
|
+
-- chance to prove it works.
|
|
698
|
+
band.Visible = true
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
--[[
|
|
702
|
+
Builds the strip. Everything except the cell's contents is created here and
|
|
703
|
+
never again, which is what keeps the per-frame path allocation-free.
|
|
704
|
+
]]
|
|
705
|
+
function Visuals.mount(parent: Instance)
|
|
706
|
+
local palette = Themes.palette()
|
|
707
|
+
|
|
708
|
+
local band = Instance.new("Frame")
|
|
709
|
+
band.Name = "ActivityBand"
|
|
710
|
+
band.BackgroundColor3 = palette.surface
|
|
711
|
+
band.BorderSizePixel = 0
|
|
712
|
+
band.Visible = false
|
|
713
|
+
band.ClipsDescendants = true
|
|
714
|
+
band.Parent = parent
|
|
715
|
+
runtime.band = band
|
|
716
|
+
|
|
717
|
+
local cell = Instance.new("Frame")
|
|
718
|
+
cell.Name = "Cell"
|
|
719
|
+
cell.Position = UDim2.fromOffset(INSET, (Visuals.BAND_HEIGHT - CELL) / 2)
|
|
720
|
+
cell.Size = UDim2.fromOffset(CELL, CELL)
|
|
721
|
+
cell.BackgroundTransparency = 1
|
|
722
|
+
cell.ClipsDescendants = true
|
|
723
|
+
cell.Parent = band
|
|
724
|
+
runtime.cell = cell
|
|
725
|
+
|
|
726
|
+
-- A hairline between the cell and the trace, so the strip reads as two
|
|
727
|
+
-- instruments rather than one busy rectangle. Centred in the gutter, which
|
|
728
|
+
-- is what keeps the extra margin as air on both sides rather than as a gap
|
|
729
|
+
-- on one.
|
|
730
|
+
local divider = Instance.new("Frame")
|
|
731
|
+
divider.Name = "Divider"
|
|
732
|
+
divider.Position = UDim2.fromOffset(INSET + CELL + GUTTER / 2, 10)
|
|
733
|
+
divider.Size = UDim2.new(0, 1, 1, -20)
|
|
734
|
+
divider.BackgroundColor3 = palette.dim
|
|
735
|
+
divider.BackgroundTransparency = 0.78
|
|
736
|
+
divider.BorderSizePixel = 0
|
|
737
|
+
divider.Parent = band
|
|
738
|
+
runtime.divider = divider
|
|
739
|
+
|
|
740
|
+
local right = INSET + CELL + GUTTER
|
|
741
|
+
|
|
742
|
+
local caption = Instance.new("TextLabel")
|
|
743
|
+
caption.Name = "Caption"
|
|
744
|
+
caption.Position = UDim2.new(0, right, 0, 6)
|
|
745
|
+
caption.Size = UDim2.new(1, -right - 12, 0, 14)
|
|
746
|
+
caption.BackgroundTransparency = 1
|
|
747
|
+
caption.Font = Enum.Font.Code
|
|
748
|
+
caption.TextSize = 11
|
|
749
|
+
caption.TextColor3 = palette.text
|
|
750
|
+
caption.TextXAlignment = Enum.TextXAlignment.Left
|
|
751
|
+
caption.TextTruncate = Enum.TextTruncate.AtEnd
|
|
752
|
+
caption.Text = "idle"
|
|
753
|
+
caption.Parent = band
|
|
754
|
+
runtime.caption = caption
|
|
755
|
+
|
|
756
|
+
local trace = Instance.new("Frame")
|
|
757
|
+
trace.Name = "Trace"
|
|
758
|
+
trace.Position = UDim2.new(0, right, 0, 24)
|
|
759
|
+
trace.Size = UDim2.new(1, -right - 12, 0, TRACE_HEIGHT)
|
|
760
|
+
trace.BackgroundTransparency = 1
|
|
761
|
+
trace.ClipsDescendants = true
|
|
762
|
+
trace.Parent = band
|
|
763
|
+
runtime.trace = trace
|
|
764
|
+
|
|
765
|
+
-- A baseline under the bars, so an empty trace still reads as an instrument
|
|
766
|
+
-- waiting for data rather than as a blank gap.
|
|
767
|
+
local baseline = Instance.new("Frame")
|
|
768
|
+
baseline.Name = "Baseline"
|
|
769
|
+
baseline.AnchorPoint = Vector2.new(0, 1)
|
|
770
|
+
baseline.Position = UDim2.fromScale(0, 1)
|
|
771
|
+
baseline.Size = UDim2.new(1, 0, 0, 1)
|
|
772
|
+
baseline.BackgroundColor3 = palette.dim
|
|
773
|
+
baseline.BackgroundTransparency = 0.75
|
|
774
|
+
baseline.BorderSizePixel = 0
|
|
775
|
+
baseline.Parent = trace
|
|
776
|
+
runtime.baseline = baseline
|
|
777
|
+
|
|
778
|
+
--[[
|
|
779
|
+
Built once and moved, and created BEFORE the bars exist so it sits under
|
|
780
|
+
them in draw order -- a highlight drawn over a one-pixel bar would hide the
|
|
781
|
+
very thing it is pointing at.
|
|
782
|
+
]]
|
|
783
|
+
local highlight = Instance.new("Frame")
|
|
784
|
+
highlight.Name = "Highlight"
|
|
785
|
+
highlight.AnchorPoint = Vector2.new(0.5, 1)
|
|
786
|
+
highlight.Position = UDim2.new(1, 0, 1, 0)
|
|
787
|
+
highlight.Size = UDim2.new(0, BAR_STRIDE, 1, 0)
|
|
788
|
+
highlight.BackgroundColor3 = palette.text
|
|
789
|
+
-- Faint enough not to hide the bar it sits behind, solid enough to be seen
|
|
790
|
+
-- at all.
|
|
791
|
+
highlight.BackgroundTransparency = 0.7
|
|
792
|
+
highlight.BorderSizePixel = 0
|
|
793
|
+
highlight.Visible = false
|
|
794
|
+
highlight.Parent = trace
|
|
795
|
+
runtime.highlight = highlight
|
|
796
|
+
|
|
797
|
+
--[[
|
|
798
|
+
The reading sits at the left end of the trace rather than beside the bar it
|
|
799
|
+
describes. Following the pointer would put it off the right edge for the
|
|
800
|
+
newest bars, which are the ones most often asked about, and the left end is
|
|
801
|
+
empty until forty calls have accumulated.
|
|
802
|
+
]]
|
|
803
|
+
local readout = Instance.new("TextLabel")
|
|
804
|
+
readout.Name = "Readout"
|
|
805
|
+
readout.AnchorPoint = Vector2.new(0, 0.5)
|
|
806
|
+
readout.Position = UDim2.new(0, 0, 0.5, 0)
|
|
807
|
+
-- Sized by its text now that it carries a name as well as a number. A fixed
|
|
808
|
+
-- 110px was enough for "18 ms" and truncates anything with a phrase in front
|
|
809
|
+
-- of it, which is the half worth reading.
|
|
810
|
+
readout.AutomaticSize = Enum.AutomaticSize.X
|
|
811
|
+
readout.Size = UDim2.new(0, 0, 0, 14)
|
|
812
|
+
readout.BackgroundColor3 = palette.background
|
|
813
|
+
readout.BackgroundTransparency = 0.15
|
|
814
|
+
readout.BorderSizePixel = 0
|
|
815
|
+
readout.Font = Enum.Font.Code
|
|
816
|
+
readout.TextSize = 11
|
|
817
|
+
readout.TextXAlignment = Enum.TextXAlignment.Left
|
|
818
|
+
readout.Text = ""
|
|
819
|
+
readout.Visible = false
|
|
820
|
+
readout.ZIndex = 3
|
|
821
|
+
readout.Parent = trace
|
|
822
|
+
runtime.readout = readout
|
|
823
|
+
|
|
824
|
+
local readoutPadding = Instance.new("UIPadding")
|
|
825
|
+
readoutPadding.PaddingLeft = UDim.new(0, 4)
|
|
826
|
+
readoutPadding.PaddingRight = UDim.new(0, 4)
|
|
827
|
+
readoutPadding.Parent = readout
|
|
828
|
+
|
|
829
|
+
local ok, err = pcall(function()
|
|
830
|
+
Themes.active().mount(cell)
|
|
831
|
+
end)
|
|
832
|
+
runtime.mounted = ok
|
|
833
|
+
if not ok then
|
|
834
|
+
warn(string.format("[rbx-studio] theme %q failed to mount: %s", Themes.activeId(), tostring(err)))
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
return Visuals
|