@craf-te/canvas-buffer-viewer 0.1.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/dist/index.js ADDED
@@ -0,0 +1,671 @@
1
+ class k {
2
+ constructor(t = 10) {
3
+ this._lastCapture = 0, this._interval = 1e3 / t;
4
+ }
5
+ setFps(t) {
6
+ this._interval = 1e3 / t;
7
+ }
8
+ shouldCapture(t) {
9
+ return t - this._lastCapture < this._interval ? !1 : (this._lastCapture = t, !0);
10
+ }
11
+ }
12
+ const I = (
13
+ /* css */
14
+ `
15
+ :host {
16
+ position: fixed;
17
+ pointer-events: none;
18
+ z-index: 99999;
19
+ font-family: monospace;
20
+ font-size: 12px;
21
+ color: #fff;
22
+ /* initial placeholder to not break while drag happens on the panel itself */
23
+ inset: 0;
24
+ }
25
+
26
+ .fbv-panel {
27
+ position: absolute;
28
+ pointer-events: auto;
29
+ background: rgba(0, 0, 0, 0.75);
30
+ border: 1px solid rgba(255, 255, 255, 0.25);
31
+ border-radius: 4px;
32
+ overflow: visible;
33
+ display: flex;
34
+ flex-direction: column;
35
+ min-width: 200px;
36
+ min-height: 120px;
37
+ transition: width 0.3s ease, height 0.3s ease, top 0.3s ease, left 0.3s ease;
38
+ }
39
+
40
+ /* Bottom position: header at bottom, content opens upward */
41
+ :host([bottom]) .fbv-panel {
42
+ flex-direction: column-reverse;
43
+ }
44
+
45
+ :host([bottom]) .fbv-header {
46
+ border-radius: 0 0 4px 4px;
47
+ }
48
+
49
+ :host([bottom]) .fbv-grid {
50
+ border-radius: 4px 4px 0 0;
51
+ }
52
+
53
+ /* Invisible hit area for edge dragging */
54
+ .fbv-panel::before {
55
+ content: '';
56
+ position: absolute;
57
+ inset: -8px;
58
+ cursor: grab;
59
+ pointer-events: auto;
60
+ z-index: -1;
61
+ }
62
+
63
+ .fbv-panel:active::before {
64
+ cursor: grabbing;
65
+ }
66
+
67
+ /* Clip inner content to panel bounds */
68
+ .fbv-panel .fbv-grid {
69
+ overflow: hidden;
70
+ }
71
+
72
+ /* Disable transition when dragging/resizing for immediate feedback */
73
+ .fbv-panel.dragging, .fbv-panel.resizing {
74
+ transition: none;
75
+ }
76
+
77
+ /* Maximize state overrides */
78
+ :host([maximized]) .fbv-panel {
79
+ top: 0 !important;
80
+ left: 0 !important;
81
+ width: 100vw !important;
82
+ height: 100vh !important;
83
+ border-radius: 0;
84
+ border: none;
85
+ }
86
+
87
+ :host([minimized]) .fbv-panel {
88
+ min-height: auto;
89
+ height: auto !important;
90
+ }
91
+
92
+ :host([minimized]) .fbv-grid,
93
+ :host([minimized]) .fbv-resize {
94
+ display: none;
95
+ }
96
+
97
+ .fbv-header {
98
+ display: flex;
99
+ align-items: center;
100
+ padding: 2px 6px;
101
+ background: rgba(255, 255, 255, 0.1);
102
+ cursor: grab;
103
+ user-select: none;
104
+ font-size: 11px;
105
+ line-height: 18px;
106
+ }
107
+
108
+ .fbv-header:active {
109
+ cursor: grabbing;
110
+ }
111
+
112
+ .fbv-header-title {
113
+ flex: 1;
114
+ white-space: nowrap;
115
+ overflow: hidden;
116
+ text-overflow: ellipsis;
117
+ }
118
+
119
+ .fbv-header-btn {
120
+ flex-shrink: 0;
121
+ width: 18px;
122
+ height: 18px;
123
+ margin-left: 2px;
124
+ padding: 0;
125
+ border: none;
126
+ background: none;
127
+ color: rgba(255, 255, 255, 0.6);
128
+ font-size: 14px;
129
+ line-height: 18px;
130
+ text-align: center;
131
+ cursor: pointer;
132
+ border-radius: 2px;
133
+ }
134
+
135
+ .fbv-header-btn:hover {
136
+ background: rgba(255, 255, 255, 0.15);
137
+ color: #fff;
138
+ }
139
+
140
+ .fbv-header-btn.back-btn {
141
+ margin-right: 6px;
142
+ font-size: 16px;
143
+ }
144
+
145
+ .fbv-grid {
146
+ flex: 1;
147
+ display: grid;
148
+ gap: 4px;
149
+ padding: 4px;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .fbv-grid.single-view {
154
+ display: block;
155
+ padding: 0;
156
+ }
157
+
158
+ .fbv-grid.single-view fbv-thumbnail {
159
+ display: none;
160
+ }
161
+
162
+ .fbv-grid.single-view fbv-thumbnail.selected {
163
+ display: flex;
164
+ height: 100%;
165
+ width: 100%;
166
+ }
167
+
168
+ /* Hover effect for thumbnails in list view */
169
+ .fbv-grid:not(.single-view) fbv-thumbnail:hover {
170
+ outline: 2px solid rgba(100, 180, 255, 0.7);
171
+ outline-offset: -2px;
172
+ }
173
+
174
+ .fbv-resize {
175
+ position: absolute;
176
+ width: 16px;
177
+ height: 16px;
178
+ }
179
+
180
+ .fbv-resize-tl {
181
+ top: -4px;
182
+ left: -4px;
183
+ cursor: nwse-resize;
184
+ }
185
+
186
+ .fbv-resize-tr {
187
+ top: -4px;
188
+ right: -4px;
189
+ cursor: nesw-resize;
190
+ }
191
+
192
+ .fbv-resize-bl {
193
+ bottom: -4px;
194
+ left: -4px;
195
+ cursor: nesw-resize;
196
+ }
197
+
198
+ .fbv-resize-br {
199
+ bottom: -4px;
200
+ right: -4px;
201
+ cursor: nwse-resize;
202
+ }
203
+ `
204
+ ), T = (
205
+ /* css */
206
+ `
207
+ :host {
208
+ display: flex;
209
+ flex-direction: column;
210
+ overflow: hidden;
211
+ min-height: 0;
212
+ font-family: monospace;
213
+ color: #fff;
214
+ cursor: pointer;
215
+ }
216
+
217
+ .fbv-buffer-header {
218
+ display: flex;
219
+ align-items: center;
220
+ background: rgba(0, 0, 0, 0.4);
221
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
222
+ }
223
+
224
+ .fbv-buffer-label {
225
+ padding: 2px 4px;
226
+ font-size: 10px;
227
+ line-height: 14px;
228
+ opacity: 0.8;
229
+ white-space: nowrap;
230
+ flex-shrink: 0;
231
+ }
232
+
233
+ .fbv-buffer-note {
234
+ flex-grow: 1;
235
+ margin-left: 4px;
236
+ overflow: hidden;
237
+ white-space: nowrap;
238
+ font-size: 10px;
239
+ color: #bbb;
240
+ position: relative;
241
+ border-left: 1px solid rgba(255, 255, 255, 0.2);
242
+ padding-left: 4px;
243
+ cursor: help;
244
+ }
245
+
246
+ .fbv-buffer-note-text {
247
+ display: inline-block;
248
+ padding-right: 100%;
249
+ animation: fbv-marquee 10s linear infinite;
250
+ }
251
+
252
+ .fbv-buffer-note:hover .fbv-buffer-note-text {
253
+ animation-play-state: paused;
254
+ }
255
+
256
+ .fbv-buffer-note::after {
257
+ content: attr(title);
258
+ position: absolute;
259
+ left: 4px;
260
+ top: 100%;
261
+ margin-top: 2px;
262
+ background: rgba(0, 0, 0, 0.9);
263
+ color: #fff;
264
+ padding: 4px 6px;
265
+ border-radius: 2px;
266
+ border: 1px solid rgba(255, 255, 255, 0.3);
267
+ font-size: 10px;
268
+ white-space: normal;
269
+ word-wrap: break-word;
270
+ max-width: 200px;
271
+ z-index: 100000;
272
+ opacity: 0;
273
+ pointer-events: none;
274
+ transition: opacity 0.2s;
275
+ display: none;
276
+ }
277
+
278
+ .fbv-buffer-note:hover::after {
279
+ display: block;
280
+ opacity: 1;
281
+ }
282
+
283
+ @keyframes fbv-marquee {
284
+ 0% { transform: translateX(0); }
285
+ 100% { transform: translateX(-100%); }
286
+ }
287
+
288
+ canvas {
289
+ flex: 1;
290
+ width: 100%;
291
+ min-height: 0;
292
+ display: block;
293
+ image-rendering: pixelated;
294
+ object-fit: contain;
295
+ }
296
+ `
297
+ );
298
+ class A extends HTMLElement {
299
+ constructor() {
300
+ super(), this._label = "", this._lastWidth = 0, this._lastHeight = 0, this._lastNote = void 0, this.attachShadow({ mode: "open" });
301
+ }
302
+ connectedCallback() {
303
+ this._render();
304
+ }
305
+ set label(t) {
306
+ this._label = t, this._labelEl && this._updateLabelText();
307
+ }
308
+ get label() {
309
+ return this._label;
310
+ }
311
+ get lastWidth() {
312
+ return this._lastWidth;
313
+ }
314
+ get lastHeight() {
315
+ return this._lastHeight;
316
+ }
317
+ _render() {
318
+ this.shadowRoot && (this.shadowRoot.innerHTML = `
319
+ <style>${T}</style>
320
+ <div class="fbv-buffer-header">
321
+ <div class="fbv-buffer-label"></div>
322
+ <div class="fbv-buffer-note" style="display: none;">
323
+ <span class="fbv-buffer-note-text"></span>
324
+ </div>
325
+ </div>
326
+ <canvas></canvas>
327
+ `, this._labelEl = this.shadowRoot.querySelector(".fbv-buffer-label"), this._noteEl = this.shadowRoot.querySelector(".fbv-buffer-note"), this._noteTextEl = this.shadowRoot.querySelector(".fbv-buffer-note-text"), this.canvas = this.shadowRoot.querySelector("canvas"), this.ctx = this.canvas.getContext("2d"), this._updateLabelText());
328
+ }
329
+ _updateLabelText() {
330
+ this._lastWidth > 0 && this._lastHeight > 0 ? this._labelEl.textContent = `${this._label} (${this._lastWidth}x${this._lastHeight})` : this._labelEl.textContent = this._label;
331
+ }
332
+ updateImage(t, e, i, s = !0, n) {
333
+ if (!this.canvas) return;
334
+ (this.canvas.width !== e || this.canvas.height !== i) && (this.canvas.width = e, this.canvas.height = i), (this._lastWidth !== e || this._lastHeight !== i) && (this._lastWidth = e, this._lastHeight = i, this._updateLabelText()), this._lastNote !== n && (this._lastNote = n, n ? (this._noteEl.style.display = "block", this._noteTextEl.textContent = n, this._noteEl.title = n, this._noteTextEl.style.animation = "none", this._noteTextEl.offsetWidth, this._noteTextEl.style.animation = "") : (this._noteEl.style.display = "none", this._noteTextEl.textContent = "", this._noteEl.title = ""));
335
+ const o = this.ctx.createImageData(e, i);
336
+ if (s)
337
+ for (let r = 0; r < i; r++) {
338
+ const d = (i - 1 - r) * e * 4, l = r * e * 4;
339
+ o.data.set(t.subarray(d, d + e * 4), l);
340
+ }
341
+ else
342
+ o.data.set(t);
343
+ this.ctx.putImageData(o, 0, 0);
344
+ }
345
+ dispose() {
346
+ this.remove();
347
+ }
348
+ }
349
+ customElements.get("fbv-thumbnail") || customElements.define("fbv-thumbnail", A);
350
+ const R = "fbv-state", f = 420, u = 240, v = 12, M = 4, g = 4, C = 22;
351
+ function H(a, t, e) {
352
+ if (e <= 0) return { cols: 1, rows: 1, cellArea: 0 };
353
+ if (e === 1) {
354
+ const o = a - g * 2, r = t - g * 2;
355
+ return { cols: 1, rows: 1, cellArea: o * r };
356
+ }
357
+ const i = a - g * 2, s = t - g * 2;
358
+ let n = { cols: 1, rows: e, cellArea: 0 };
359
+ for (let o = 1; o <= e; o++) {
360
+ const r = Math.ceil(e / o), d = (i - M * (o - 1)) / o, l = (s - M * (r - 1)) / r;
361
+ if (d < 60 || l < 40) continue;
362
+ const h = d * l;
363
+ h > n.cellArea && (n = { cols: o, rows: r, cellArea: h });
364
+ }
365
+ return n;
366
+ }
367
+ class $ extends HTMLElement {
368
+ constructor() {
369
+ super(), this._corner = "top-right", this._initialPosSet = !1, this._preMaxState = null, this._selectedLabel = null, this._resizeObserver = null, this.attachShadow({ mode: "open" }), this._state = this._loadState(), this._boundEscHandler = (t) => {
370
+ t.key === "Escape" && (this._selectedLabel !== null ? this.deselectItem() : this._state.maximized && this._toggleMaximize(!1));
371
+ };
372
+ }
373
+ connectedCallback() {
374
+ this._render(), this._applyBottomAttribute(), this._applyState(), document.addEventListener("keydown", this._boundEscHandler), this._initResizeObserver();
375
+ }
376
+ disconnectedCallback() {
377
+ var t;
378
+ document.removeEventListener("keydown", this._boundEscHandler), (t = this._resizeObserver) == null || t.disconnect(), this._resizeObserver = null;
379
+ }
380
+ set corner(t) {
381
+ this._corner = t, this._applyBottomAttribute(), !this._initialPosSet && this._state.top === null && this._state.left === null && this._applyDefaultCornerPos();
382
+ }
383
+ _applyBottomAttribute() {
384
+ this._corner.indexOf("bottom") >= 0 ? this.setAttribute("bottom", "") : this.removeAttribute("bottom");
385
+ }
386
+ _render() {
387
+ if (!this.shadowRoot) return;
388
+ this.shadowRoot.innerHTML = `
389
+ <style>${I}</style>
390
+ <div class="fbv-panel">
391
+ <div class="fbv-header">
392
+ <button class="fbv-header-btn back-btn" title="リストに戻る" style="display:none;">←</button>
393
+ <span class="fbv-header-title">Framebuffer Viewer</span>
394
+ <button class="fbv-header-btn reset-btn" title="Reset Position">↺</button>
395
+ <button class="fbv-header-btn minimize-btn" title="Minimize">−</button>
396
+ <button class="fbv-header-btn restore-btn" title="Restore" style="display:none;">□</button>
397
+ <button class="fbv-header-btn maximize-btn" title="Maximize">▣</button>
398
+ <button class="fbv-header-btn unmaximize-btn" title="Unmaximize" style="display:none;">▨</button>
399
+ </div>
400
+ <div class="fbv-grid"></div>
401
+ <div class="fbv-resize fbv-resize-tl"></div>
402
+ <div class="fbv-resize fbv-resize-tr"></div>
403
+ <div class="fbv-resize fbv-resize-bl"></div>
404
+ <div class="fbv-resize fbv-resize-br"></div>
405
+ </div>
406
+ `, this._panelEl = this.shadowRoot.querySelector(".fbv-panel"), this._gridEl = this.shadowRoot.querySelector(".fbv-grid"), this._minimizeBtn = this.shadowRoot.querySelector(".minimize-btn"), this._restoreBtn = this.shadowRoot.querySelector(".restore-btn"), this._maximizeBtn = this.shadowRoot.querySelector(".maximize-btn"), this._unmaximizeBtn = this.shadowRoot.querySelector(".unmaximize-btn"), this._backBtn = this.shadowRoot.querySelector(".back-btn");
407
+ const t = this.shadowRoot.querySelector(".fbv-header"), e = this.shadowRoot.querySelector(".reset-btn");
408
+ this._initDrag(t), this._initResize(), this._minimizeBtn.addEventListener("click", (i) => {
409
+ i.stopPropagation(), this._toggleMinimize(!0);
410
+ }), this._restoreBtn.addEventListener("click", (i) => {
411
+ i.stopPropagation(), this._toggleMinimize(!1);
412
+ }), this._maximizeBtn.addEventListener("click", (i) => {
413
+ i.stopPropagation(), this._toggleMaximize(!0);
414
+ }), this._unmaximizeBtn.addEventListener("click", (i) => {
415
+ i.stopPropagation(), this._toggleMaximize(!1);
416
+ }), e.addEventListener("click", (i) => {
417
+ i.stopPropagation(), this._resetState();
418
+ }), this._backBtn.addEventListener("click", (i) => {
419
+ i.stopPropagation(), this.deselectItem();
420
+ }), this._gridEl.addEventListener("click", (i) => {
421
+ const s = i.target.closest("fbv-thumbnail");
422
+ if (!s) return;
423
+ const n = s.dataset.label;
424
+ n && (this._selectedLabel === n ? this.deselectItem() : this.selectItem(n));
425
+ });
426
+ }
427
+ // --- External API ---
428
+ addThumbnail(t) {
429
+ const e = document.createElement("fbv-thumbnail");
430
+ return e.label = t, e.dataset.label = t, this._gridEl.appendChild(e), this._updateGridColumns(), this._selectedLabel === t && this.selectItem(t), e;
431
+ }
432
+ removeThumbnail(t) {
433
+ const e = this._gridEl.querySelector(`fbv-thumbnail[data-label="${t}"]`);
434
+ e && (this._selectedLabel === t && this.deselectItem(), e.remove(), this._updateGridColumns());
435
+ }
436
+ get items() {
437
+ return this._gridEl ? Array.from(this._gridEl.querySelectorAll("fbv-thumbnail")) : [];
438
+ }
439
+ getItem(t) {
440
+ return this.items.find((e) => e.label === t);
441
+ }
442
+ dispose() {
443
+ this.remove();
444
+ }
445
+ // --- Selection API ---
446
+ selectItem(t) {
447
+ const e = this.getItem(t);
448
+ e && (this.items.forEach((i) => i.classList.remove("selected")), this._selectedLabel = t, e.classList.add("selected"), this._gridEl.classList.add("single-view"), this._backBtn.style.display = "", this._state.selectedLabel = t, this._saveState());
449
+ }
450
+ deselectItem() {
451
+ this._selectedLabel = null, this.items.forEach((t) => t.classList.remove("selected")), this._gridEl.classList.remove("single-view"), this._backBtn.style.display = "none", this._state.selectedLabel = null, this._saveState();
452
+ }
453
+ // --- Internal State & Layout ---
454
+ _updateGridColumns() {
455
+ if (!this._gridEl || !this._panelEl) return;
456
+ const t = this._panelEl.clientWidth, e = this._panelEl.clientHeight - C, i = H(t, e, this.items.length);
457
+ this._gridEl.style.gridTemplateColumns = `repeat(${i.cols}, 1fr)`, this._gridEl.style.gridTemplateRows = `repeat(${i.rows}, 1fr)`;
458
+ }
459
+ _initResizeObserver() {
460
+ this._panelEl && (this._resizeObserver = new ResizeObserver(() => {
461
+ this._updateGridColumns();
462
+ }), this._resizeObserver.observe(this._panelEl));
463
+ }
464
+ _loadState() {
465
+ try {
466
+ const t = sessionStorage.getItem(R);
467
+ if (t) {
468
+ const e = JSON.parse(t);
469
+ return "selectedLabel" in e || (e.selectedLabel = null), e;
470
+ }
471
+ } catch {
472
+ }
473
+ return {
474
+ width: f,
475
+ height: u,
476
+ top: null,
477
+ left: null,
478
+ minimized: !1,
479
+ maximized: !1,
480
+ selectedLabel: null
481
+ };
482
+ }
483
+ _saveState() {
484
+ try {
485
+ sessionStorage.setItem(R, JSON.stringify(this._state));
486
+ } catch {
487
+ }
488
+ }
489
+ _applyState() {
490
+ this._state.width && this._state.height && (this._panelEl.style.width = `${this._state.width}px`, this._panelEl.style.height = `${this._state.height}px`), this._state.top !== null && this._state.left !== null ? (this._panelEl.style.top = `${this._state.top}px`, this._panelEl.style.left = `${this._state.left}px`, this._initialPosSet = !0) : this._applyDefaultCornerPos(), this._toggleMinimize(this._state.minimized, !1), this._toggleMaximize(this._state.maximized, !1), this._selectedLabel = this._state.selectedLabel;
491
+ }
492
+ _resetState() {
493
+ this._state = {
494
+ width: f,
495
+ height: u,
496
+ top: null,
497
+ left: null,
498
+ minimized: !1,
499
+ maximized: !1,
500
+ selectedLabel: null
501
+ }, this._initialPosSet = !1, this._preMaxState = null, this.deselectItem(), this._applyDefaultCornerPos(), this._applyState(), this._saveState();
502
+ }
503
+ _applyDefaultCornerPos() {
504
+ if (!this._panelEl) return;
505
+ this._panelEl.style.width = `${f}px`, this._panelEl.style.height = `${u}px`, this._panelEl.style.right = "", this._panelEl.style.bottom = "";
506
+ const t = this._corner.indexOf("top") >= 0, e = this._corner.indexOf("left") >= 0, i = t ? v : window.innerHeight - u - v, s = e ? v : window.innerWidth - f - v;
507
+ this._panelEl.style.top = `${i}px`, this._panelEl.style.left = `${s}px`, this._state.top = i, this._state.left = s, this._state.width = f, this._state.height = u;
508
+ }
509
+ _toggleMinimize(t, e = !0) {
510
+ this._state.minimized = t, t ? (this.setAttribute("minimized", ""), this._minimizeBtn.style.display = "none", this._restoreBtn.style.display = "", this._state.maximized && this._toggleMaximize(!1, !1)) : (this.removeAttribute("minimized"), this._minimizeBtn.style.display = "", this._restoreBtn.style.display = "none"), e && this._saveState();
511
+ }
512
+ _toggleMaximize(t, e = !0) {
513
+ this._state.maximized = t, t ? (this.hasAttribute("maximized") || (this._preMaxState = {
514
+ width: this._state.width,
515
+ height: this._state.height,
516
+ top: this._state.top,
517
+ left: this._state.left
518
+ }), this.setAttribute("maximized", ""), this._maximizeBtn.style.display = "none", this._unmaximizeBtn.style.display = "", this._state.minimized && this._toggleMinimize(!1, !1)) : (this.removeAttribute("maximized"), this._maximizeBtn.style.display = "", this._unmaximizeBtn.style.display = "none", this._preMaxState && (this._state.width = this._preMaxState.width, this._state.height = this._preMaxState.height, this._state.top = this._preMaxState.top, this._state.left = this._preMaxState.left, this._panelEl.style.width = `${this._state.width}px`, this._panelEl.style.height = `${this._state.height}px`, this._panelEl.style.top = `${this._state.top}px`, this._panelEl.style.left = `${this._state.left}px`)), e && this._saveState();
519
+ }
520
+ // --- Drag & Drop ---
521
+ _initDrag(t) {
522
+ let e = 0, i = 0, s = 0, n = 0;
523
+ const o = (l) => {
524
+ let h = s + (l.clientX - e), b = n + (l.clientY - i);
525
+ this._panelEl.style.left = `${h}px`, this._panelEl.style.top = `${b}px`;
526
+ }, r = () => {
527
+ this._panelEl.classList.remove("dragging"), document.removeEventListener("mousemove", o), document.removeEventListener("mouseup", r);
528
+ const l = this._panelEl.getBoundingClientRect();
529
+ this._state.left = l.left, this._state.top = l.top, this._saveState();
530
+ }, d = (l) => {
531
+ if (l.target.tagName.toLowerCase() === "button" || this._state.maximized) return;
532
+ l.preventDefault(), this._panelEl.classList.add("dragging");
533
+ const h = this._panelEl.getBoundingClientRect();
534
+ e = l.clientX, i = l.clientY, s = h.left, n = h.top, document.addEventListener("mousemove", o), document.addEventListener("mouseup", r);
535
+ };
536
+ t.addEventListener("mousedown", d), this._panelEl.addEventListener("mousedown", (l) => {
537
+ const h = l.target;
538
+ h.closest(".fbv-grid") || h.closest(".fbv-resize") || h.closest(".fbv-header") || h.closest("fbv-thumbnail") || d(l);
539
+ });
540
+ }
541
+ // --- Resize ---
542
+ _initResize() {
543
+ ["tl", "tr", "bl", "br"].forEach((e) => {
544
+ var s;
545
+ const i = (s = this.shadowRoot) == null ? void 0 : s.querySelector(`.fbv-resize-${e}`);
546
+ i && i.addEventListener("mousedown", (n) => {
547
+ if (this._state.maximized) return;
548
+ n.preventDefault(), n.stopPropagation(), this._panelEl.classList.add("resizing");
549
+ const o = n.clientX, r = n.clientY, d = this._panelEl.getBoundingClientRect(), l = d.width, h = d.height, b = d.top, x = d.left, y = (p) => {
550
+ const E = p.clientX - o, z = p.clientY - r;
551
+ let _ = l, m = h, L = b, S = x;
552
+ e === "tl" || e === "bl" ? (_ = Math.max(200, l - E), S = x + (l - _)) : _ = Math.max(200, l + E), e === "tl" || e === "tr" ? (m = Math.max(120, h - z), L = b + (h - m)) : m = Math.max(120, h + z), this._panelEl.style.width = `${_}px`, this._panelEl.style.height = `${m}px`, this._panelEl.style.top = `${L}px`, this._panelEl.style.left = `${S}px`;
553
+ }, w = () => {
554
+ this._panelEl.classList.remove("resizing"), document.removeEventListener("mousemove", y), document.removeEventListener("mouseup", w);
555
+ const p = this._panelEl.getBoundingClientRect();
556
+ this._state.width = p.width, this._state.height = p.height, this._state.top = p.top, this._state.left = p.left, this._saveState();
557
+ };
558
+ document.addEventListener("mousemove", y), document.addEventListener("mouseup", w);
559
+ });
560
+ });
561
+ }
562
+ }
563
+ customElements.get("fbv-panel") || customElements.define("fbv-panel", $);
564
+ class D {
565
+ constructor(t = "top-right") {
566
+ this._panel = null, this._corner = t;
567
+ }
568
+ mount() {
569
+ this._panel || (this._panel = document.createElement("fbv-panel"), this._panel.corner = this._corner, document.body.appendChild(this._panel));
570
+ }
571
+ unmount() {
572
+ this._panel && (this._panel.remove(), this._panel = null);
573
+ }
574
+ addItem(t) {
575
+ if (!this._panel)
576
+ throw new Error("[BufferViewer] Not mounted yet.");
577
+ const e = this._panel.getItem(t);
578
+ return e || this._panel.addThumbnail(t);
579
+ }
580
+ removeItem(t) {
581
+ this._panel && this._panel.removeThumbnail(t);
582
+ }
583
+ getItem(t) {
584
+ var e;
585
+ return (e = this._panel) == null ? void 0 : e.getItem(t);
586
+ }
587
+ get items() {
588
+ return this._panel ? this._panel.items.values() : [].values();
589
+ }
590
+ dispose() {
591
+ this.unmount();
592
+ }
593
+ }
594
+ const c = class c {
595
+ constructor(t = {}) {
596
+ this._slots = /* @__PURE__ */ new Map(), this._disposed = !1, this._fps = t.fps ?? 10, this._overlay = new D(t.corner ?? "top-right"), this._active = !1, t.active !== !1 && (this.active = !0);
597
+ }
598
+ static getInstance(t) {
599
+ return c._instance || (c._instance = new c(t)), c._instance;
600
+ }
601
+ get active() {
602
+ return this._active;
603
+ }
604
+ set active(t) {
605
+ this._disposed || (this._active = t, t ? this._overlay.mount() : this._overlay.unmount());
606
+ }
607
+ toggle() {
608
+ this.active = !this._active;
609
+ }
610
+ setFps(t) {
611
+ this._fps = t;
612
+ for (const e of this._slots.values())
613
+ e.scheduler.setFps(t);
614
+ }
615
+ /**
616
+ * Capture and display pixel data under the given label.
617
+ * The getData callback is only invoked when the FPS throttle allows.
618
+ * It should return { data, width, height } with RGBA pixel data.
619
+ *
620
+ * Call this at any point in your render pipeline.
621
+ * The panel is auto-created on first use for each label.
622
+ */
623
+ capture(t, e, i) {
624
+ if (!this._active || this._disposed) return;
625
+ const s = this._getOrCreateSlot(t), n = performance.now();
626
+ if (!s.scheduler.shouldCapture(n)) return;
627
+ const o = e(), r = o.width ?? s.panel.lastWidth, d = o.height ?? s.panel.lastHeight;
628
+ if (!r || !d)
629
+ throw new Error(
630
+ `[BufferViewer] "${t}": width/height required on first capture`
631
+ );
632
+ s.panel.updateImage(o.data, r, d, o.flipY ?? !0, i);
633
+ }
634
+ removeBuffer(t) {
635
+ this._slots.has(t) && (this._overlay.removeItem(t), this._slots.delete(t));
636
+ }
637
+ dispose() {
638
+ this._disposed = !0, this._active = !1, this._slots.clear(), this._overlay.dispose(), c._instance = null;
639
+ }
640
+ _getOrCreateSlot(t) {
641
+ let e = this._slots.get(t);
642
+ return e || (e = {
643
+ scheduler: new k(this._fps),
644
+ panel: this._overlay.addItem(t)
645
+ }, this._slots.set(t, e)), e;
646
+ }
647
+ };
648
+ c._instance = null;
649
+ let B = c;
650
+ function P(a, t) {
651
+ const e = t.width, i = t.height, s = new Uint8Array(e * i * 4);
652
+ return a.readRenderTargetPixels(t, 0, 0, e, i, s), { data: s, width: e, height: i };
653
+ }
654
+ function q(a, t) {
655
+ const e = a.getParameter(a.FRAMEBUFFER_BINDING);
656
+ t !== void 0 && a.bindFramebuffer(a.FRAMEBUFFER, t);
657
+ const i = a.drawingBufferWidth, s = a.drawingBufferHeight, n = new Uint8Array(i * s * 4);
658
+ return a.readPixels(0, 0, i, s, a.RGBA, a.UNSIGNED_BYTE, n), t !== void 0 && a.bindFramebuffer(a.FRAMEBUFFER, e), { data: n, width: i, height: s };
659
+ }
660
+ function F(a) {
661
+ let t;
662
+ a instanceof HTMLCanvasElement || a instanceof OffscreenCanvas ? t = a.getContext("2d") : t = a;
663
+ const e = t.canvas.width, i = t.canvas.height, s = t.getImageData(0, 0, e, i);
664
+ return { data: new Uint8Array(s.data.buffer), width: e, height: i, flipY: !1 };
665
+ }
666
+ export {
667
+ B as BufferViewer,
668
+ F as readCanvas,
669
+ q as readPixels,
670
+ P as readRenderTarget
671
+ };