@h-rig/cli 0.0.6-alpha.85 → 0.0.6-alpha.87
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/bin/rig.js +79 -1027
- package/dist/src/app-opentui/bootstrap.js +79 -1027
- package/dist/src/app-opentui/command-palette.js +1 -0
- package/dist/src/app-opentui/drone.js +1 -0
- package/dist/src/app-opentui/index.js +61 -1024
- package/dist/src/app-opentui/list-search.d.ts +5 -1
- package/dist/src/app-opentui/list-search.js +2 -2
- package/dist/src/app-opentui/react/App.js +62 -970
- package/dist/src/app-opentui/react/Backdrop.js +3 -9
- package/dist/src/app-opentui/react/ChromeHost.js +6 -1038
- package/dist/src/app-opentui/react/SceneFrameView.js +55 -5
- package/dist/src/app-opentui/react/launch.js +62 -970
- package/dist/src/app-opentui/react/syntax.js +1 -0
- package/dist/src/app-opentui/registry.js +3 -5
- package/dist/src/app-opentui/render/constants.d.ts +0 -1
- package/dist/src/app-opentui/render/constants.js +0 -2
- package/dist/src/app-opentui/render/graphics.js +3 -9
- package/dist/src/app-opentui/render/hover.d.ts +11 -0
- package/dist/src/app-opentui/render/hover.js +34 -0
- package/dist/src/app-opentui/render/native-host.js +1 -0
- package/dist/src/app-opentui/render/panels.js +50 -3
- package/dist/src/app-opentui/render/preloader.js +1 -0
- package/dist/src/app-opentui/render/scene.js +1 -0
- package/dist/src/app-opentui/render/text.d.ts +13 -0
- package/dist/src/app-opentui/render/text.js +54 -5
- package/dist/src/app-opentui/render/type-bar.js +1 -0
- package/dist/src/app-opentui/runtime.js +62 -1025
- package/dist/src/app-opentui/scenes/command.js +1 -0
- package/dist/src/app-opentui/scenes/doctor.js +1 -0
- package/dist/src/app-opentui/scenes/error.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/agent.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/browser.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/dist.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/git.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/github.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/harness.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/index.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/kit.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/profile.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/queue.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/remote.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/review.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/setup.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/stats.js +1 -0
- package/dist/src/app-opentui/scenes/family.js +1 -0
- package/dist/src/app-opentui/scenes/fleet.js +3 -5
- package/dist/src/app-opentui/scenes/handoff.js +1 -0
- package/dist/src/app-opentui/scenes/help.js +1 -0
- package/dist/src/app-opentui/scenes/inbox.js +1 -0
- package/dist/src/app-opentui/scenes/init.js +1 -0
- package/dist/src/app-opentui/scenes/inspect.js +1 -0
- package/dist/src/app-opentui/scenes/main.js +1 -0
- package/dist/src/app-opentui/scenes/pi.js +1 -0
- package/dist/src/app-opentui/scenes/plugin.js +1 -0
- package/dist/src/app-opentui/scenes/repo.js +1 -0
- package/dist/src/app-opentui/scenes/run-detail.js +1 -0
- package/dist/src/app-opentui/scenes/server.js +1 -0
- package/dist/src/app-opentui/scenes/tasks.js +3 -5
- package/dist/src/app-opentui/scenes/workspace.js +1 -0
- package/dist/src/app-opentui/selectable.js +1 -0
- package/dist/src/app-opentui/theme.d.ts +1 -0
- package/dist/src/app-opentui/theme.js +1 -0
- package/package.json +8 -8
- package/dist/src/app-opentui/render/image-visual-layer-worker.d.ts +0 -1
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +0 -1084
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +0 -109
- package/dist/src/app-opentui/render/image-visual-layer.js +0 -1068
- package/dist/src/app-opentui/terminal-capabilities.d.ts +0 -7
- package/dist/src/app-opentui/terminal-capabilities.js +0 -15
|
@@ -2242,6 +2242,7 @@ var RIG_UI = {
|
|
|
2242
2242
|
bg2: "#0b0c0e",
|
|
2243
2243
|
panel: "#101115",
|
|
2244
2244
|
panel2: "#14161b",
|
|
2245
|
+
hover: "#181b24",
|
|
2245
2246
|
glass: "#1e2230",
|
|
2246
2247
|
border: "#2a2e3a",
|
|
2247
2248
|
ink: "#f4f5f8",
|
|
@@ -2773,19 +2774,6 @@ function panelContentRect(layout, panel) {
|
|
|
2773
2774
|
const contentWidth = Math.max(1, width - paddingX * 2 - 1);
|
|
2774
2775
|
return { left, top, width, height, paddingX, paddingY, contentWidth };
|
|
2775
2776
|
}
|
|
2776
|
-
function panelPixelPlacement(layout, panel, size, minPixels = 8) {
|
|
2777
|
-
const cellW = size.width / layout.width;
|
|
2778
|
-
const cellH = size.height / layout.height;
|
|
2779
|
-
const leftCells = layout.centerLeft + (panel.left ?? 0);
|
|
2780
|
-
const topCells = layout.centerTop + panel.top;
|
|
2781
|
-
const widthCells = panel.width ?? layout.centerWidth;
|
|
2782
|
-
const heightCells = panel.height;
|
|
2783
|
-
const left = Math.round(leftCells * cellW);
|
|
2784
|
-
const top = Math.round(topCells * cellH);
|
|
2785
|
-
const width = Math.round(widthCells * cellW);
|
|
2786
|
-
const height = Math.round(heightCells * cellH);
|
|
2787
|
-
return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
|
|
2788
|
-
}
|
|
2789
2777
|
|
|
2790
2778
|
// packages/cli/src/app-opentui/render/constants.ts
|
|
2791
2779
|
var LCG_MULTIPLIER = 1664525;
|
|
@@ -2818,7 +2806,6 @@ var RAIL_YS_LOOP = [0.18, 0.5, 0.82];
|
|
|
2818
2806
|
var RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
|
|
2819
2807
|
var RAIL_YS_DISPATCH = [0.18, 0.58, 0.82];
|
|
2820
2808
|
var GLOW_FALLOFF_EXP_ASCII = 2.2;
|
|
2821
|
-
var GLOW_FALLOFF_EXP_IMAGE = 2.4;
|
|
2822
2809
|
|
|
2823
2810
|
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2824
2811
|
var BRAILLE_SAMPLES_X = 2;
|
|
@@ -3250,27 +3237,20 @@ function writeCell(layer, x, y, char, fg, bg) {
|
|
|
3250
3237
|
function drawFullCell(layer, x, y, bg) {
|
|
3251
3238
|
writeCell(layer, x, y, " ", TRANSPARENT, bg);
|
|
3252
3239
|
}
|
|
3253
|
-
function
|
|
3254
|
-
writeCell(layer, x, y, char, fg, BACKDROP);
|
|
3255
|
-
}
|
|
3256
|
-
function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
|
|
3240
|
+
function drawPanelFill(layer, left, top, right, bottom, dividerY) {
|
|
3257
3241
|
const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
|
|
3258
3242
|
for (let y = top;y <= bottom; y += 1) {
|
|
3259
3243
|
const bg = bgForY(y);
|
|
3260
3244
|
for (let x = left;x <= right; x += 1)
|
|
3261
3245
|
drawFullCell(layer, x, y, bg);
|
|
3262
3246
|
}
|
|
3263
|
-
drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
|
|
3264
|
-
drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
|
|
3265
|
-
drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
|
|
3266
|
-
drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
|
|
3267
3247
|
}
|
|
3268
3248
|
function drawAdTerminalPanel(layer, layout, panel) {
|
|
3269
3249
|
const rect = panelCellRect(layout, panel);
|
|
3270
3250
|
if (!rect)
|
|
3271
3251
|
return;
|
|
3272
3252
|
const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
|
|
3273
|
-
|
|
3253
|
+
drawPanelFill(layer, left, top, right, bottom, dividerY);
|
|
3274
3254
|
if (dividerY > top && dividerY < bottom) {
|
|
3275
3255
|
for (let x = left + 2;x < right - 1; x += 1) {
|
|
3276
3256
|
writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
|
|
@@ -3417,982 +3397,68 @@ function sparklineString(values, width) {
|
|
|
3417
3397
|
}).join("");
|
|
3418
3398
|
}
|
|
3419
3399
|
|
|
3420
|
-
// packages/cli/src/app-opentui/render/
|
|
3421
|
-
import {
|
|
3422
|
-
import { deflateSync } from "zlib";
|
|
3423
|
-
import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
|
|
3424
|
-
|
|
3425
|
-
// packages/cli/src/app-opentui/terminal-capabilities.ts
|
|
3426
|
-
import { getCapabilities } from "@earendil-works/pi-tui";
|
|
3427
|
-
function getRigTerminalCapabilities() {
|
|
3428
|
-
const imageProtocol = getCapabilities().images;
|
|
3429
|
-
const supportsKittyImages = imageProtocol === "kitty";
|
|
3430
|
-
return {
|
|
3431
|
-
imageProtocol,
|
|
3432
|
-
supportsKittyImages,
|
|
3433
|
-
visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
|
|
3434
|
-
};
|
|
3435
|
-
}
|
|
3400
|
+
// packages/cli/src/app-opentui/render/text.ts
|
|
3401
|
+
import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
|
|
3436
3402
|
|
|
3437
|
-
// packages/cli/src/app-opentui/render/
|
|
3438
|
-
var
|
|
3439
|
-
var
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
var PANEL = [16, 17, 21, 184];
|
|
3446
|
-
var PANEL_HEADER = [16, 17, 21, 190];
|
|
3447
|
-
var PANEL_LINE2 = [255, 255, 255, 24];
|
|
3448
|
-
var LIME = [204, 255, 77, 255];
|
|
3449
|
-
var LIME_DIM = [169, 214, 63, 255];
|
|
3450
|
-
var CYAN = [86, 216, 255, 255];
|
|
3451
|
-
var MAGENTA = [255, 121, 176, 255];
|
|
3452
|
-
var INK_DIM = [108, 110, 121, 255];
|
|
3453
|
-
var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
3454
|
-
var CRC_TABLE = makeCrcTable();
|
|
3455
|
-
var MAX_IMAGE_BYTES = 48 * 1024 * 1024;
|
|
3456
|
-
var KITTY_CHUNK_SIZE = 4096;
|
|
3457
|
-
var PANEL_BLUR_SCALE = 1;
|
|
3458
|
-
var PANEL_MASK_CACHE_LIMIT = 16;
|
|
3459
|
-
var ZLIB_LEVEL = 1;
|
|
3460
|
-
var DEFAULT_IMAGE_RESOLUTION_SCALE = 1;
|
|
3461
|
-
var LOAD_BUCKETS = 5;
|
|
3462
|
-
function loadBucket(load) {
|
|
3463
|
-
if (!Number.isFinite(load ?? NaN))
|
|
3464
|
-
return 0;
|
|
3465
|
-
return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
|
|
3466
|
-
}
|
|
3467
|
-
var panelMaskCache = new Map;
|
|
3468
|
-
var BG_WORD = rgbaWord(BG);
|
|
3469
|
-
function imageTransport() {
|
|
3470
|
-
const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
|
|
3471
|
-
if (forced === "rgba-zlib")
|
|
3472
|
-
return "rgba-zlib";
|
|
3473
|
-
return "png";
|
|
3474
|
-
}
|
|
3475
|
-
function imageResolutionScale() {
|
|
3476
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_SCALE ?? "");
|
|
3477
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
3478
|
-
return Math.max(0.25, Math.min(1, raw));
|
|
3479
|
-
return DEFAULT_IMAGE_RESOLUTION_SCALE;
|
|
3480
|
-
}
|
|
3481
|
-
function rgbaWord(color) {
|
|
3482
|
-
return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
|
|
3483
|
-
}
|
|
3484
|
-
function fillRgba(data, color, word = rgbaWord(color)) {
|
|
3485
|
-
if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
|
|
3486
|
-
new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
|
|
3403
|
+
// packages/cli/src/app-opentui/render/hover.ts
|
|
3404
|
+
var hoveredId;
|
|
3405
|
+
var listeners = new Set;
|
|
3406
|
+
function isHovered(id) {
|
|
3407
|
+
return id !== undefined && id === hoveredId;
|
|
3408
|
+
}
|
|
3409
|
+
function setHoveredSelectableId(id) {
|
|
3410
|
+
if (hoveredId === id)
|
|
3487
3411
|
return;
|
|
3488
|
-
|
|
3489
|
-
for (
|
|
3490
|
-
|
|
3491
|
-
data[i + 1] = color[1];
|
|
3492
|
-
data[i + 2] = color[2];
|
|
3493
|
-
data[i + 3] = color[3];
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
var ASCII_GLYPHS = {
|
|
3497
|
-
".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
|
|
3498
|
-
"'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
|
|
3499
|
-
"-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
|
|
3500
|
-
_: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
|
|
3501
|
-
"=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
|
|
3502
|
-
"|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
|
|
3503
|
-
"/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
|
|
3504
|
-
"\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
|
|
3505
|
-
"(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
|
|
3506
|
-
")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
|
|
3507
|
-
"!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
|
|
3508
|
-
"%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
|
|
3509
|
-
$: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
|
|
3510
|
-
"@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
|
|
3511
|
-
o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
|
|
3512
|
-
"*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
|
|
3513
|
-
};
|
|
3514
|
-
function sceneStaticTick(scene) {
|
|
3515
|
-
let hash = 0;
|
|
3516
|
-
for (let index = 0;index < scene.length; index += 1)
|
|
3517
|
-
hash = hash * 33 + scene.charCodeAt(index) >>> 0;
|
|
3518
|
-
return 11 + hash % 97;
|
|
3519
|
-
}
|
|
3520
|
-
function charColor(char, row, totalRows) {
|
|
3521
|
-
if (char === "@" || char === "$" || char === "o")
|
|
3522
|
-
return LIME;
|
|
3523
|
-
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
3524
|
-
return CYAN;
|
|
3525
|
-
if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
|
|
3526
|
-
return INK_DIM;
|
|
3527
|
-
if (char === "-" || char === "=" || char === "(" || char === ")")
|
|
3528
|
-
return LIME_DIM;
|
|
3529
|
-
return MAGENTA;
|
|
3530
|
-
}
|
|
3531
|
-
function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
|
|
3532
|
-
const x0 = Math.max(0, Math.floor(left));
|
|
3533
|
-
const y0 = Math.max(0, Math.floor(top));
|
|
3534
|
-
const x1 = Math.min(width, Math.ceil(left + rectWidth));
|
|
3535
|
-
const y1 = Math.min(height, Math.ceil(top + rectHeight));
|
|
3536
|
-
for (let y = y0;y < y1; y += 1) {
|
|
3537
|
-
for (let x = x0;x < x1; x += 1)
|
|
3538
|
-
blendOver(data, (y * width + x) * 4, color, alpha);
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
|
|
3542
|
-
const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
|
|
3543
|
-
const glyphWidth = Math.max(1, cellWidth * 0.56);
|
|
3544
|
-
const glyphHeight = Math.max(1, cellHeight * 0.7);
|
|
3545
|
-
const originX = x + (cellWidth - glyphWidth) * 0.5;
|
|
3546
|
-
const originY = y + (cellHeight - glyphHeight) * 0.5;
|
|
3547
|
-
const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
|
|
3548
|
-
const pixelHeight = Math.max(1, glyphHeight / pattern.length);
|
|
3549
|
-
for (let row = 0;row < pattern.length; row += 1) {
|
|
3550
|
-
const bits = pattern[row];
|
|
3551
|
-
for (let col = 0;col < bits.length; col += 1) {
|
|
3552
|
-
if (bits[col] !== "1")
|
|
3553
|
-
continue;
|
|
3554
|
-
fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
|
|
3559
|
-
const rows = fleetRows(sceneStaticTick(scene), { animate: true });
|
|
3560
|
-
const cellWidth = width / Math.max(1, layout.width);
|
|
3561
|
-
const cellHeight = height / Math.max(1, layout.height);
|
|
3562
|
-
const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
|
|
3563
|
-
const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
|
|
3564
|
-
const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
|
|
3565
|
-
const fleetLeft = leftCells * cellWidth;
|
|
3566
|
-
const fleetTop = topCells * cellHeight;
|
|
3567
|
-
drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
|
|
3568
|
-
drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
|
|
3569
|
-
drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
|
|
3570
|
-
let droneTotal = 0;
|
|
3571
|
-
for (const line2 of rows)
|
|
3572
|
-
for (const char of line2)
|
|
3573
|
-
if (char === "@" || char === "$" || char === "%")
|
|
3574
|
-
droneTotal += 1;
|
|
3575
|
-
const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
|
|
3576
|
-
let droneIndex = 0;
|
|
3577
|
-
for (let row = 0;row < rows.length; row += 1) {
|
|
3578
|
-
const line2 = rows[row];
|
|
3579
|
-
for (let col = 0;col < line2.length; col += 1) {
|
|
3580
|
-
const char = line2[col];
|
|
3581
|
-
if (char === " ")
|
|
3582
|
-
continue;
|
|
3583
|
-
const x = fleetLeft + col * cellWidth;
|
|
3584
|
-
const y = fleetTop + row * cellHeight;
|
|
3585
|
-
const color = charColor(char, row, rows.length);
|
|
3586
|
-
const isDrone = char === "@" || char === "$" || char === "%";
|
|
3587
|
-
const lit = isDrone && droneIndex < litTarget;
|
|
3588
|
-
drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
|
|
3589
|
-
if (isDrone) {
|
|
3590
|
-
droneIndex += 1;
|
|
3591
|
-
drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
function applyStaticAtmosphere(data, width, height, load) {
|
|
3597
|
-
const cx = width * 0.5;
|
|
3598
|
-
const cy = height * 0.42;
|
|
3599
|
-
const radius = Math.max(width, height) * 0.78;
|
|
3600
|
-
const minVignette = 0.1 - load * 0.02;
|
|
3601
|
-
for (let y = 0;y < height; y += 1) {
|
|
3602
|
-
for (let x = 0;x < width; x += 1) {
|
|
3603
|
-
const distance = Math.hypot(x - cx, y - cy) / radius;
|
|
3604
|
-
const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
|
|
3605
|
-
blendOver(data, (y * width + x) * 4, BG, alpha);
|
|
3606
|
-
}
|
|
3607
|
-
}
|
|
3608
|
-
drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
|
|
3609
|
-
drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
|
|
3610
|
-
}
|
|
3611
|
-
function makeCrcTable() {
|
|
3612
|
-
const table = new Uint32Array(256);
|
|
3613
|
-
for (let n = 0;n < 256; n += 1) {
|
|
3614
|
-
let c = n;
|
|
3615
|
-
for (let k = 0;k < 8; k += 1)
|
|
3616
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3617
|
-
table[n] = c >>> 0;
|
|
3618
|
-
}
|
|
3619
|
-
return table;
|
|
3620
|
-
}
|
|
3621
|
-
function crc32(bytes) {
|
|
3622
|
-
let c = 4294967295;
|
|
3623
|
-
for (let i = 0;i < bytes.length; i += 1)
|
|
3624
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3625
|
-
return (c ^ 4294967295) >>> 0;
|
|
3626
|
-
}
|
|
3627
|
-
function chunk2(type, data) {
|
|
3628
|
-
const out = new Uint8Array(12 + data.length);
|
|
3629
|
-
const view = new DataView(out.buffer);
|
|
3630
|
-
view.setUint32(0, data.length);
|
|
3631
|
-
out[4] = type.charCodeAt(0);
|
|
3632
|
-
out[5] = type.charCodeAt(1);
|
|
3633
|
-
out[6] = type.charCodeAt(2);
|
|
3634
|
-
out[7] = type.charCodeAt(3);
|
|
3635
|
-
out.set(data, 8);
|
|
3636
|
-
view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
3637
|
-
return out;
|
|
3638
|
-
}
|
|
3639
|
-
function encodePng(width, height, rgba) {
|
|
3640
|
-
const ihdr = new Uint8Array(13);
|
|
3641
|
-
const ihdrView = new DataView(ihdr.buffer);
|
|
3642
|
-
ihdrView.setUint32(0, width);
|
|
3643
|
-
ihdrView.setUint32(4, height);
|
|
3644
|
-
ihdr[8] = 8;
|
|
3645
|
-
ihdr[9] = 6;
|
|
3646
|
-
ihdr[10] = 0;
|
|
3647
|
-
ihdr[11] = 0;
|
|
3648
|
-
ihdr[12] = 0;
|
|
3649
|
-
const stride = width * 4;
|
|
3650
|
-
const raw = new Uint8Array((stride + 1) * height);
|
|
3651
|
-
for (let y = 0;y < height; y += 1) {
|
|
3652
|
-
const rawOffset = y * (stride + 1);
|
|
3653
|
-
raw[rawOffset] = 0;
|
|
3654
|
-
raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
|
|
3655
|
-
}
|
|
3656
|
-
const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
|
|
3657
|
-
return Buffer.concat([
|
|
3658
|
-
Buffer.from(PNG_SIGNATURE),
|
|
3659
|
-
Buffer.from(chunk2("IHDR", ihdr)),
|
|
3660
|
-
Buffer.from(chunk2("IDAT", compressed)),
|
|
3661
|
-
Buffer.from(chunk2("IEND", new Uint8Array(0)))
|
|
3662
|
-
]);
|
|
3663
|
-
}
|
|
3664
|
-
function kittyImageSequence(base64, options) {
|
|
3665
|
-
const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
|
|
3666
|
-
if (options.format === 32) {
|
|
3667
|
-
params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
|
|
3668
|
-
if (options.compressed)
|
|
3669
|
-
params.push("o=z");
|
|
3670
|
-
}
|
|
3671
|
-
if (base64.length <= KITTY_CHUNK_SIZE)
|
|
3672
|
-
return `\x1B_G${params.join(",")};${base64}\x1B\\`;
|
|
3673
|
-
const chunks = [];
|
|
3674
|
-
for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
|
|
3675
|
-
const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
|
|
3676
|
-
const last = offset + KITTY_CHUNK_SIZE >= base64.length;
|
|
3677
|
-
chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
|
|
3678
|
-
}
|
|
3679
|
-
return chunks.join("");
|
|
3680
|
-
}
|
|
3681
|
-
function clampByte(value) {
|
|
3682
|
-
return Math.max(0, Math.min(255, Math.round(value)));
|
|
3683
|
-
}
|
|
3684
|
-
function blendOver(data, index, color, alphaScale = 1) {
|
|
3685
|
-
const alpha = color[3] / 255 * alphaScale;
|
|
3686
|
-
const inv = 1 - alpha;
|
|
3687
|
-
data[index] = clampByte(color[0] * alpha + data[index] * inv);
|
|
3688
|
-
data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
|
|
3689
|
-
data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
|
|
3690
|
-
data[index + 3] = 255;
|
|
3691
|
-
}
|
|
3692
|
-
function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
|
|
3693
|
-
const minX = Math.max(0, Math.floor(cx - radius));
|
|
3694
|
-
const maxX = Math.min(width - 1, Math.ceil(cx + radius));
|
|
3695
|
-
const minY = Math.max(0, Math.floor(cy - radius));
|
|
3696
|
-
const maxY = Math.min(height - 1, Math.ceil(cy + radius));
|
|
3697
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
3698
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
3699
|
-
const dx = x + 0.5 - cx;
|
|
3700
|
-
const dy = y + 0.5 - cy;
|
|
3701
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
3702
|
-
if (d > radius)
|
|
3703
|
-
continue;
|
|
3704
|
-
const edge = Math.max(0, Math.min(1, radius - d));
|
|
3705
|
-
blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3412
|
+
hoveredId = id;
|
|
3413
|
+
for (const listener of listeners)
|
|
3414
|
+
listener();
|
|
3708
3415
|
}
|
|
3709
|
-
function
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
const minY = Math.max(0, Math.floor(cy - radius));
|
|
3713
|
-
const maxY = Math.min(height - 1, Math.ceil(cy + radius));
|
|
3714
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
3715
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
3716
|
-
const dx = x + 0.5 - cx;
|
|
3717
|
-
const dy = y + 0.5 - cy;
|
|
3718
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
3719
|
-
if (d > radius)
|
|
3720
|
-
continue;
|
|
3721
|
-
const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
|
|
3722
|
-
blendOver(data, (y * width + x) * 4, color, alpha * falloff);
|
|
3723
|
-
}
|
|
3724
|
-
}
|
|
3416
|
+
function clearHoveredSelectableId(id) {
|
|
3417
|
+
if (id !== undefined && hoveredId === id)
|
|
3418
|
+
setHoveredSelectableId(undefined);
|
|
3725
3419
|
}
|
|
3726
|
-
function
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
for (let i = 0;i <= steps; i += 1) {
|
|
3731
|
-
const t2 = i / steps;
|
|
3732
|
-
drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
function roundRectAlpha(px, py, x, y, w, h, r) {
|
|
3736
|
-
const rx = Math.max(x + r, Math.min(px, x + w - r));
|
|
3737
|
-
const ry = Math.max(y + r, Math.min(py, y + h - r));
|
|
3738
|
-
const dx = px - rx;
|
|
3739
|
-
const dy = py - ry;
|
|
3740
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
3741
|
-
return Math.max(0, Math.min(1, r + 0.75 - dist));
|
|
3742
|
-
}
|
|
3743
|
-
function panelRect(layout, panel, size) {
|
|
3744
|
-
return panelPixelPlacement(layout, panel, size);
|
|
3745
|
-
}
|
|
3746
|
-
function downsampleRegion(data, canvasW, canvasH, rect, scale) {
|
|
3747
|
-
const w = Math.max(1, Math.ceil(rect.width / scale));
|
|
3748
|
-
const h = Math.max(1, Math.ceil(rect.height / scale));
|
|
3749
|
-
const out = new Uint8ClampedArray(w * h * 3);
|
|
3750
|
-
for (let y = 0;y < h; y += 1) {
|
|
3751
|
-
for (let x = 0;x < w; x += 1) {
|
|
3752
|
-
let r = 0, g = 0, b = 0, count = 0;
|
|
3753
|
-
const sx0 = rect.left + x * scale;
|
|
3754
|
-
const sy0 = rect.top + y * scale;
|
|
3755
|
-
for (let yy = 0;yy < scale; yy += 1) {
|
|
3756
|
-
const sy = sy0 + yy;
|
|
3757
|
-
if (sy < 0 || sy >= canvasH)
|
|
3758
|
-
continue;
|
|
3759
|
-
for (let xx = 0;xx < scale; xx += 1) {
|
|
3760
|
-
const sx = sx0 + xx;
|
|
3761
|
-
if (sx < 0 || sx >= canvasW)
|
|
3762
|
-
continue;
|
|
3763
|
-
const i = (sy * canvasW + sx) * 4;
|
|
3764
|
-
r += data[i];
|
|
3765
|
-
g += data[i + 1];
|
|
3766
|
-
b += data[i + 2];
|
|
3767
|
-
count += 1;
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
|
-
const oi = (y * w + x) * 3;
|
|
3771
|
-
out[oi] = count ? r / count : BG[0];
|
|
3772
|
-
out[oi + 1] = count ? g / count : BG[1];
|
|
3773
|
-
out[oi + 2] = count ? b / count : BG[2];
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
return { pixels: out, width: w, height: h };
|
|
3777
|
-
}
|
|
3778
|
-
function blurRgb(buffer, width, height, radius) {
|
|
3779
|
-
const tmp = new Uint8ClampedArray(buffer.length);
|
|
3780
|
-
const out = new Uint8ClampedArray(buffer.length);
|
|
3781
|
-
const window = radius * 2 + 1;
|
|
3782
|
-
for (let y = 0;y < height; y += 1) {
|
|
3783
|
-
let r = 0, g = 0, b = 0;
|
|
3784
|
-
for (let x = -radius;x <= radius; x += 1) {
|
|
3785
|
-
const sx = Math.max(0, Math.min(width - 1, x));
|
|
3786
|
-
const i = (y * width + sx) * 3;
|
|
3787
|
-
r += buffer[i];
|
|
3788
|
-
g += buffer[i + 1];
|
|
3789
|
-
b += buffer[i + 2];
|
|
3790
|
-
}
|
|
3791
|
-
for (let x = 0;x < width; x += 1) {
|
|
3792
|
-
const oi = (y * width + x) * 3;
|
|
3793
|
-
tmp[oi] = r / window;
|
|
3794
|
-
tmp[oi + 1] = g / window;
|
|
3795
|
-
tmp[oi + 2] = b / window;
|
|
3796
|
-
const removeX = Math.max(0, x - radius);
|
|
3797
|
-
const addX = Math.min(width - 1, x + radius + 1);
|
|
3798
|
-
const ri = (y * width + removeX) * 3;
|
|
3799
|
-
const ai = (y * width + addX) * 3;
|
|
3800
|
-
r += tmp.length ? buffer[ai] - buffer[ri] : 0;
|
|
3801
|
-
g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
|
|
3802
|
-
b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
for (let x = 0;x < width; x += 1) {
|
|
3806
|
-
let r = 0, g = 0, b = 0;
|
|
3807
|
-
for (let y = -radius;y <= radius; y += 1) {
|
|
3808
|
-
const sy = Math.max(0, Math.min(height - 1, y));
|
|
3809
|
-
const i = (sy * width + x) * 3;
|
|
3810
|
-
r += tmp[i];
|
|
3811
|
-
g += tmp[i + 1];
|
|
3812
|
-
b += tmp[i + 2];
|
|
3813
|
-
}
|
|
3814
|
-
for (let y = 0;y < height; y += 1) {
|
|
3815
|
-
const oi = (y * width + x) * 3;
|
|
3816
|
-
out[oi] = r / window;
|
|
3817
|
-
out[oi + 1] = g / window;
|
|
3818
|
-
out[oi + 2] = b / window;
|
|
3819
|
-
const removeY = Math.max(0, y - radius);
|
|
3820
|
-
const addY = Math.min(height - 1, y + radius + 1);
|
|
3821
|
-
const ri = (removeY * width + x) * 3;
|
|
3822
|
-
const ai = (addY * width + x) * 3;
|
|
3823
|
-
r += tmp[ai] - tmp[ri];
|
|
3824
|
-
g += tmp[ai + 1] - tmp[ri + 1];
|
|
3825
|
-
b += tmp[ai + 2] - tmp[ri + 2];
|
|
3826
|
-
}
|
|
3827
|
-
}
|
|
3828
|
-
return out;
|
|
3829
|
-
}
|
|
3830
|
-
function panelMaskKey(width, height, rect, headerPx, blurScale) {
|
|
3831
|
-
return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
|
|
3832
|
-
}
|
|
3833
|
-
function getPanelMask(width, height, rect, headerPx, blurScale) {
|
|
3834
|
-
const key = panelMaskKey(width, height, rect, headerPx, blurScale);
|
|
3835
|
-
const cached = panelMaskCache.get(key);
|
|
3836
|
-
if (cached)
|
|
3837
|
-
return cached;
|
|
3838
|
-
const radius = Math.max(PANEL_MASK_RADIUS_MIN, Math.min(PANEL_MASK_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_MASK_RADIUS_FACTOR)));
|
|
3839
|
-
const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
|
|
3840
|
-
const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
|
|
3841
|
-
const x0 = Math.max(0, rect.left);
|
|
3842
|
-
const y0 = Math.max(0, rect.top);
|
|
3843
|
-
const x1 = Math.min(width - 1, rect.left + rect.width - 1);
|
|
3844
|
-
const y1 = Math.min(height - 1, rect.top + rect.height - 1);
|
|
3845
|
-
const indices = [];
|
|
3846
|
-
const sampleIndices = [];
|
|
3847
|
-
const panelAlpha = [];
|
|
3848
|
-
const edgeAlpha = [];
|
|
3849
|
-
const headerFlags = [];
|
|
3850
|
-
const dividerIndices = [];
|
|
3851
|
-
for (let y = y0;y <= y1; y += 1) {
|
|
3852
|
-
for (let x = x0;x <= x1; x += 1) {
|
|
3853
|
-
const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
|
|
3854
|
-
if (a <= 0)
|
|
3855
|
-
continue;
|
|
3856
|
-
const edge = roundRectAlpha(x + 0.5, y + 0.5, rect.left + 1.2, rect.top + 1.2, rect.width - 2.4, rect.height - 2.4, Math.max(1, radius - 1.2));
|
|
3857
|
-
const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
|
|
3858
|
-
const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
|
|
3859
|
-
indices.push((y * width + x) * 4);
|
|
3860
|
-
sampleIndices.push((sy * sampledWidth + sx) * 3);
|
|
3861
|
-
panelAlpha.push(a);
|
|
3862
|
-
edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
|
|
3863
|
-
headerFlags.push(y < rect.top + headerPx ? 1 : 0);
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
const dividerY = Math.round(rect.top + headerPx);
|
|
3867
|
-
if (dividerY > y0 && dividerY < y1) {
|
|
3868
|
-
for (let x = x0 + radius;x <= x1 - radius; x += 1) {
|
|
3869
|
-
dividerIndices.push((dividerY * width + x) * 4);
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
const mask = {
|
|
3873
|
-
key,
|
|
3874
|
-
indices: Uint32Array.from(indices),
|
|
3875
|
-
sampleIndices: Uint32Array.from(sampleIndices),
|
|
3876
|
-
panelAlpha: Float32Array.from(panelAlpha),
|
|
3877
|
-
edgeAlpha: Float32Array.from(edgeAlpha),
|
|
3878
|
-
headerFlags: Uint8Array.from(headerFlags),
|
|
3879
|
-
dividerIndices: Uint32Array.from(dividerIndices)
|
|
3420
|
+
function subscribeHover(listener) {
|
|
3421
|
+
listeners.add(listener);
|
|
3422
|
+
return () => {
|
|
3423
|
+
listeners.delete(listener);
|
|
3880
3424
|
};
|
|
3881
|
-
panelMaskCache.set(key, mask);
|
|
3882
|
-
while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
|
|
3883
|
-
const oldest = panelMaskCache.keys().next().value;
|
|
3884
|
-
if (!oldest)
|
|
3885
|
-
break;
|
|
3886
|
-
panelMaskCache.delete(oldest);
|
|
3887
|
-
}
|
|
3888
|
-
return mask;
|
|
3889
|
-
}
|
|
3890
|
-
function compositePanel(data, width, height, rect, headerPx) {
|
|
3891
|
-
const blurScale = PANEL_BLUR_SCALE;
|
|
3892
|
-
const sampled = downsampleRegion(data, width, height, rect, blurScale);
|
|
3893
|
-
const blurRadius = Math.max(PANEL_BLUR_RADIUS_MIN, Math.min(PANEL_BLUR_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_BLUR_RADIUS_FACTOR)));
|
|
3894
|
-
const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
|
|
3895
|
-
const mask = getPanelMask(width, height, rect, headerPx, blurScale);
|
|
3896
|
-
for (let i = 0;i < mask.indices.length; i += 1) {
|
|
3897
|
-
const idx = mask.indices[i];
|
|
3898
|
-
const sampleIdx = mask.sampleIndices[i];
|
|
3899
|
-
data[idx] = blurred[sampleIdx];
|
|
3900
|
-
data[idx + 1] = blurred[sampleIdx + 1];
|
|
3901
|
-
data[idx + 2] = blurred[sampleIdx + 2];
|
|
3902
|
-
data[idx + 3] = 255;
|
|
3903
|
-
blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
|
|
3904
|
-
const edge = mask.edgeAlpha[i];
|
|
3905
|
-
if (edge > 0)
|
|
3906
|
-
blendOver(data, idx, PANEL_LINE2, edge);
|
|
3907
|
-
}
|
|
3908
|
-
for (let i = 0;i < mask.dividerIndices.length; i += 1) {
|
|
3909
|
-
blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
var RIG_GLYPH_STROKES = {
|
|
3913
|
-
R: [[0, 0, 0, 1], [0, 0, 0.82, 0], [0.82, 0, 0.82, 0.5], [0, 0.5, 0.82, 0.5], [0.36, 0.5, 0.92, 1]],
|
|
3914
|
-
I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
|
|
3915
|
-
G: [[0.92, 0.08, 0.12, 0], [0.12, 0, 0.12, 1], [0.12, 1, 0.92, 1], [0.92, 0.5, 0.92, 1], [0.5, 0.5, 0.92, 0.5]]
|
|
3916
|
-
};
|
|
3917
|
-
function drawBigRigWordmark(data, width, height, layout, load) {
|
|
3918
|
-
const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
|
|
3919
|
-
if (!sideBySide)
|
|
3920
|
-
return;
|
|
3921
|
-
const letters = "RIG";
|
|
3922
|
-
const min = Math.min(width, height);
|
|
3923
|
-
const glyphH = height * 0.42;
|
|
3924
|
-
const glyphW = width * 0.13;
|
|
3925
|
-
const gap = glyphW * 0.3;
|
|
3926
|
-
const totalW = letters.length * glyphW + (letters.length - 1) * gap;
|
|
3927
|
-
const centerX = width * 0.71;
|
|
3928
|
-
const startX = centerX - totalW / 2;
|
|
3929
|
-
const top = (height - glyphH) / 2;
|
|
3930
|
-
const stroke = Math.max(2, min * 0.013);
|
|
3931
|
-
drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
|
|
3932
|
-
const strokeAlpha = 0.1 + load * 0.06;
|
|
3933
|
-
letters.split("").forEach((letter, index) => {
|
|
3934
|
-
const segments = RIG_GLYPH_STROKES[letter];
|
|
3935
|
-
if (!segments)
|
|
3936
|
-
return;
|
|
3937
|
-
const ox = startX + index * (glyphW + gap);
|
|
3938
|
-
for (const [x0, y0, x1, y1] of segments) {
|
|
3939
|
-
drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
|
|
3940
|
-
}
|
|
3941
|
-
});
|
|
3942
|
-
}
|
|
3943
|
-
function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
|
|
3944
|
-
fillRgba(data, BG, BG_WORD);
|
|
3945
|
-
drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
|
|
3946
|
-
drawBigRigWordmark(data, width, height, layout, load);
|
|
3947
|
-
applyStaticAtmosphere(data, width, height, load);
|
|
3948
|
-
}
|
|
3949
|
-
function imageVisualFrameKey(input) {
|
|
3950
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
3951
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
3952
|
-
const layer = "layer" in input ? input.layer ?? "full" : "full";
|
|
3953
|
-
const load = loadBucket(input.load);
|
|
3954
|
-
return `${layer}:${width}x${height}:${input.layout.width}x${input.layout.height}:${input.scene}:${input.tick}:l${load}:${input.panels.map((panel) => `${panel.id}:${panel.top}:${panel.height}:${panel.width ?? ""}:${panel.left ?? ""}:${panel.headerHeight ?? ""}`).join("|")}`;
|
|
3955
|
-
}
|
|
3956
|
-
function encodeKittyFrame(width, height, data, input) {
|
|
3957
|
-
if (input.transport === "png") {
|
|
3958
|
-
const png = encodePng(width, height, data);
|
|
3959
|
-
return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
|
|
3960
|
-
}
|
|
3961
|
-
const compressed = deflateSync(data, { level: ZLIB_LEVEL });
|
|
3962
|
-
return kittyImageSequence(compressed.toString("base64"), {
|
|
3963
|
-
columns: input.layout.width,
|
|
3964
|
-
rows: input.layout.height,
|
|
3965
|
-
imageId: input.imageId,
|
|
3966
|
-
zIndex: input.zIndex,
|
|
3967
|
-
format: 32,
|
|
3968
|
-
pixelWidth: width,
|
|
3969
|
-
pixelHeight: height,
|
|
3970
|
-
compressed: true
|
|
3971
|
-
});
|
|
3972
|
-
}
|
|
3973
|
-
function cropRgba(data, sourceWidth, rect) {
|
|
3974
|
-
const out = new Uint8ClampedArray(rect.width * rect.height * 4);
|
|
3975
|
-
for (let y = 0;y < rect.height; y += 1) {
|
|
3976
|
-
const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
|
|
3977
|
-
out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
|
|
3978
|
-
}
|
|
3979
|
-
return out;
|
|
3980
|
-
}
|
|
3981
|
-
function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
|
|
3982
|
-
const patch = cropRgba(data, sourceWidth, placement);
|
|
3983
|
-
const png = encodePng(placement.width, placement.height, patch);
|
|
3984
|
-
const sequence = kittyImageSequence(png.toString("base64"), {
|
|
3985
|
-
columns: Math.max(1, Math.round(placement.widthCells)),
|
|
3986
|
-
rows: Math.max(1, Math.round(placement.heightCells)),
|
|
3987
|
-
imageId,
|
|
3988
|
-
zIndex,
|
|
3989
|
-
format: 100
|
|
3990
|
-
});
|
|
3991
|
-
return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
|
|
3992
|
-
}
|
|
3993
|
-
function renderImageVisualFrame(input) {
|
|
3994
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
3995
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
3996
|
-
const byteCount = width * height * 4;
|
|
3997
|
-
if (byteCount > MAX_IMAGE_BYTES)
|
|
3998
|
-
return null;
|
|
3999
|
-
const layer = input.layer ?? "full";
|
|
4000
|
-
const data = new Uint8ClampedArray(byteCount);
|
|
4001
|
-
const load = loadBucket(input.load) / LOAD_BUCKETS;
|
|
4002
|
-
const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
|
|
4003
|
-
drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
|
|
4004
|
-
if (layer === "split") {
|
|
4005
|
-
const sequences = [encodeKittyFrame(width, height, data, input)];
|
|
4006
|
-
let panelIndex = 0;
|
|
4007
|
-
for (const panel of input.panels) {
|
|
4008
|
-
if (panel.chrome !== "ad-terminal")
|
|
4009
|
-
continue;
|
|
4010
|
-
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
4011
|
-
if (!placement)
|
|
4012
|
-
continue;
|
|
4013
|
-
const cellH = height / input.layout.height;
|
|
4014
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
4015
|
-
compositePanel(data, width, height, placement, headerPx);
|
|
4016
|
-
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
4017
|
-
sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
|
|
4018
|
-
panelIndex += 1;
|
|
4019
|
-
}
|
|
4020
|
-
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
4021
|
-
}
|
|
4022
|
-
if (layer === "panel-chrome")
|
|
4023
|
-
return { key: imageVisualFrameKey(input), sequence: "" };
|
|
4024
|
-
if (layer === "background") {
|
|
4025
|
-
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
4026
|
-
}
|
|
4027
|
-
if (layer === "panel-patches") {
|
|
4028
|
-
const sequences = [];
|
|
4029
|
-
let panelIndex = 0;
|
|
4030
|
-
for (const panel of input.panels) {
|
|
4031
|
-
if (panel.chrome !== "ad-terminal")
|
|
4032
|
-
continue;
|
|
4033
|
-
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
4034
|
-
if (!placement)
|
|
4035
|
-
continue;
|
|
4036
|
-
const cellH = height / input.layout.height;
|
|
4037
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
4038
|
-
compositePanel(data, width, height, placement, headerPx);
|
|
4039
|
-
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
4040
|
-
sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
|
|
4041
|
-
panelIndex += 1;
|
|
4042
|
-
}
|
|
4043
|
-
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
4044
|
-
}
|
|
4045
|
-
for (const panel of input.panels) {
|
|
4046
|
-
if (panel.chrome !== "ad-terminal")
|
|
4047
|
-
continue;
|
|
4048
|
-
const rect = panelRect(input.layout, panel, { width, height });
|
|
4049
|
-
if (!rect)
|
|
4050
|
-
continue;
|
|
4051
|
-
const cellH = height / input.layout.height;
|
|
4052
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
4053
|
-
compositePanel(data, width, height, rect, headerPx);
|
|
4054
|
-
}
|
|
4055
|
-
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
4056
|
-
}
|
|
4057
|
-
function shouldRenderImagesInline() {
|
|
4058
|
-
return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
|
|
4059
|
-
}
|
|
4060
|
-
function createImageWorker() {
|
|
4061
|
-
if (shouldRenderImagesInline())
|
|
4062
|
-
return null;
|
|
4063
|
-
const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
|
|
4064
|
-
const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
|
|
4065
|
-
try {
|
|
4066
|
-
return new Worker(workerUrl);
|
|
4067
|
-
} catch {
|
|
4068
|
-
if (typeof process.versions.bun === "string") {
|
|
4069
|
-
try {
|
|
4070
|
-
return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
|
|
4071
|
-
} catch {}
|
|
4072
|
-
}
|
|
4073
|
-
return null;
|
|
4074
|
-
}
|
|
4075
|
-
}
|
|
4076
|
-
var RECENT_WRITTEN_LIMIT = 24;
|
|
4077
|
-
var DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
|
|
4078
|
-
var DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
|
|
4079
|
-
function imageOutputBacklogBytes() {
|
|
4080
|
-
const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
|
|
4081
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
4082
|
-
return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
|
|
4083
|
-
return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
|
|
4084
|
-
}
|
|
4085
|
-
function imageOutputBytesPerSecond() {
|
|
4086
|
-
const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
|
|
4087
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
4088
|
-
return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
|
|
4089
|
-
return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
|
|
4090
|
-
}
|
|
4091
|
-
function pendingLayerKey(key) {
|
|
4092
|
-
return key.split(":", 1)[0] || "full";
|
|
4093
3425
|
}
|
|
4094
3426
|
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
onFrameReady;
|
|
4101
|
-
protocol;
|
|
4102
|
-
transport = imageTransport();
|
|
4103
|
-
maxOutputBacklogBytes = imageOutputBacklogBytes();
|
|
4104
|
-
outputBytesPerSecond = imageOutputBytesPerSecond();
|
|
4105
|
-
outputBudgetBytes = imageOutputBytesPerSecond();
|
|
4106
|
-
lastBudgetAtMs = Date.now();
|
|
4107
|
-
motionSlot = this.createSlot();
|
|
4108
|
-
workersStarted = false;
|
|
4109
|
-
pendingFrames = new Map;
|
|
4110
|
-
recentWrittenKeys = [];
|
|
4111
|
-
imagesVisible = false;
|
|
4112
|
-
flushPaused = false;
|
|
4113
|
-
requestId = 0;
|
|
4114
|
-
lastBackgroundKey = "";
|
|
4115
|
-
fatalWorkerError = null;
|
|
4116
|
-
destroyed = false;
|
|
4117
|
-
constructor(stdout = process.stdout, onFrameReady) {
|
|
4118
|
-
this.stdout = stdout;
|
|
4119
|
-
this.onFrameReady = onFrameReady;
|
|
4120
|
-
this.protocol = getRigTerminalCapabilities().imageProtocol;
|
|
4121
|
-
}
|
|
4122
|
-
get enabled() {
|
|
4123
|
-
return this.protocol === "kitty";
|
|
4124
|
-
}
|
|
4125
|
-
clear() {
|
|
4126
|
-
if (this.enabled) {
|
|
4127
|
-
if (this.imagesVisible)
|
|
4128
|
-
this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
|
|
4129
|
-
this.imagesVisible = false;
|
|
4130
|
-
this.pendingFrames.clear();
|
|
4131
|
-
this.recentWrittenKeys.length = 0;
|
|
4132
|
-
this.resetSlot(this.motionSlot);
|
|
4133
|
-
this.lastBackgroundKey = "";
|
|
4134
|
-
this.outputBudgetBytes = this.outputBytesPerSecond;
|
|
4135
|
-
this.lastBudgetAtMs = Date.now();
|
|
4136
|
-
this.motionSlot.clearedThroughRequestId = this.requestId;
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
destroy() {
|
|
4140
|
-
if (this.destroyed)
|
|
4141
|
-
return;
|
|
4142
|
-
this.clear();
|
|
4143
|
-
this.destroyed = true;
|
|
4144
|
-
const workers = [this.motionSlot.worker];
|
|
4145
|
-
this.motionSlot.worker = null;
|
|
4146
|
-
for (const worker of workers)
|
|
4147
|
-
if (worker)
|
|
4148
|
-
worker.terminate().catch(() => {
|
|
4149
|
-
return;
|
|
4150
|
-
});
|
|
4151
|
-
}
|
|
4152
|
-
setFlushPaused(paused) {
|
|
4153
|
-
this.flushPaused = paused;
|
|
4154
|
-
}
|
|
4155
|
-
isReady(input) {
|
|
4156
|
-
if (!this.enabled || this.destroyed)
|
|
4157
|
-
return true;
|
|
4158
|
-
this.throwIfFatal();
|
|
4159
|
-
const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
|
|
4160
|
-
return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
|
|
4161
|
-
}
|
|
4162
|
-
render(input) {
|
|
4163
|
-
if (!this.enabled || this.destroyed)
|
|
4164
|
-
return;
|
|
4165
|
-
this.throwIfFatal();
|
|
4166
|
-
if (this.transport !== "png")
|
|
4167
|
-
this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
|
|
4168
|
-
this.ensureWorkersStarted();
|
|
4169
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
4170
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
4171
|
-
if (width * height * 4 > MAX_IMAGE_BYTES)
|
|
4172
|
-
return;
|
|
4173
|
-
if (this.isOutputBackedUp())
|
|
4174
|
-
return;
|
|
4175
|
-
this.refillOutputBudget();
|
|
4176
|
-
const splitInput = { ...input, tick: 0, layer: "split" };
|
|
4177
|
-
const splitKey = imageVisualFrameKey(splitInput);
|
|
4178
|
-
if (input.force || splitKey !== this.lastBackgroundKey) {
|
|
4179
|
-
this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
|
|
4180
|
-
this.lastBackgroundKey = splitKey;
|
|
4181
|
-
}
|
|
4182
|
-
}
|
|
4183
|
-
flush() {
|
|
4184
|
-
this.throwIfFatal();
|
|
4185
|
-
if (this.flushPaused)
|
|
4186
|
-
return false;
|
|
4187
|
-
if (this.pendingFrames.size === 0)
|
|
4188
|
-
return false;
|
|
4189
|
-
this.refillOutputBudget();
|
|
4190
|
-
if (this.isOutputBackedUp()) {
|
|
4191
|
-
const retainedSplit = this.pendingFrames.get("split");
|
|
4192
|
-
this.pendingFrames.clear();
|
|
4193
|
-
if (retainedSplit)
|
|
4194
|
-
this.pendingFrames.set("split", retainedSplit);
|
|
4195
|
-
return false;
|
|
4196
|
-
}
|
|
4197
|
-
const framePriority = (frame) => {
|
|
4198
|
-
const layer = pendingLayerKey(frame.key);
|
|
4199
|
-
if (layer === "split")
|
|
4200
|
-
return 1;
|
|
4201
|
-
return 4;
|
|
4202
|
-
};
|
|
4203
|
-
const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
|
|
4204
|
-
const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
|
|
4205
|
-
const suffix = "\x1B[u\x1B8";
|
|
4206
|
-
const selected = [];
|
|
4207
|
-
const retained = new Map;
|
|
4208
|
-
let sequence = "";
|
|
4209
|
-
let byteLength = Buffer.byteLength(prefix + suffix);
|
|
4210
|
-
for (const frame of frames) {
|
|
4211
|
-
const candidate = sequence + frame.sequence;
|
|
4212
|
-
const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
|
|
4213
|
-
const layer = pendingLayerKey(frame.key);
|
|
4214
|
-
const allowOneStaticFrame = selected.length === 0 && layer === "split";
|
|
4215
|
-
if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
|
|
4216
|
-
selected.push(frame);
|
|
4217
|
-
sequence = candidate;
|
|
4218
|
-
byteLength = candidateBytes;
|
|
4219
|
-
} else if (layer === "split") {
|
|
4220
|
-
retained.set(layer, frame);
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
this.pendingFrames.clear();
|
|
4224
|
-
for (const [layer, frame] of retained)
|
|
4225
|
-
this.pendingFrames.set(layer, frame);
|
|
4226
|
-
if (!sequence || selected.length === 0)
|
|
4227
|
-
return false;
|
|
4228
|
-
const payload = `${prefix}${sequence}${suffix}`;
|
|
4229
|
-
for (const frame of selected)
|
|
4230
|
-
this.markWritten(frame.key);
|
|
4231
|
-
this.outputBudgetBytes -= byteLength;
|
|
4232
|
-
this.stdout.cork?.();
|
|
4233
|
-
const accepted = this.stdout.write(payload);
|
|
4234
|
-
this.stdout.uncork?.();
|
|
4235
|
-
this.imagesVisible = true;
|
|
4236
|
-
return accepted;
|
|
4237
|
-
}
|
|
4238
|
-
createSlot() {
|
|
4239
|
-
return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
|
|
4240
|
-
}
|
|
4241
|
-
resetSlot(slot) {
|
|
4242
|
-
slot.busy = false;
|
|
4243
|
-
slot.queuedRequest = null;
|
|
4244
|
-
slot.inFlightKey = "";
|
|
4245
|
-
slot.queuedKey = "";
|
|
4246
|
-
}
|
|
4247
|
-
markWritten(key) {
|
|
4248
|
-
this.recentWrittenKeys.push(key);
|
|
4249
|
-
while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
|
|
4250
|
-
this.recentWrittenKeys.shift();
|
|
4251
|
-
}
|
|
4252
|
-
refillOutputBudget() {
|
|
4253
|
-
const now = Date.now();
|
|
4254
|
-
const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
|
|
4255
|
-
this.lastBudgetAtMs = now;
|
|
4256
|
-
this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
|
|
4257
|
-
}
|
|
4258
|
-
isOutputBackedUp() {
|
|
4259
|
-
return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
|
|
4260
|
-
}
|
|
4261
|
-
stageFrame(frame) {
|
|
4262
|
-
this.pendingFrames.set(pendingLayerKey(frame.key), frame);
|
|
4263
|
-
}
|
|
4264
|
-
isKnownKey(slot, key) {
|
|
4265
|
-
return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
|
|
4266
|
-
}
|
|
4267
|
-
ensureWorkersStarted() {
|
|
4268
|
-
if (this.workersStarted)
|
|
4269
|
-
return;
|
|
4270
|
-
this.workersStarted = true;
|
|
4271
|
-
this.startWorker(this.motionSlot, "static-visuals");
|
|
4272
|
-
}
|
|
4273
|
-
failWorker(name, error) {
|
|
4274
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
4275
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
|
|
4276
|
-
throw this.fatalWorkerError;
|
|
4277
|
-
}
|
|
4278
|
-
throwIfFatal() {
|
|
4279
|
-
if (this.fatalWorkerError)
|
|
4280
|
-
throw this.fatalWorkerError;
|
|
4281
|
-
}
|
|
4282
|
-
scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
|
|
4283
|
-
this.throwIfFatal();
|
|
4284
|
-
const key = imageVisualFrameKey(input);
|
|
4285
|
-
if (!force && this.isKnownKey(slot, key))
|
|
4286
|
-
return;
|
|
4287
|
-
const request = {
|
|
4288
|
-
...input,
|
|
4289
|
-
imageId,
|
|
4290
|
-
...imageIds ? { imageIds } : {},
|
|
4291
|
-
zIndex,
|
|
4292
|
-
transport: this.transport,
|
|
4293
|
-
requestId: ++this.requestId,
|
|
4294
|
-
key
|
|
4295
|
-
};
|
|
4296
|
-
slot.latestRequestId = request.requestId;
|
|
4297
|
-
if (!slot.worker) {
|
|
4298
|
-
this.renderInline(slot, request);
|
|
4299
|
-
return;
|
|
4300
|
-
}
|
|
4301
|
-
this.enqueueWorkerRender(slot, request);
|
|
4302
|
-
}
|
|
4303
|
-
startWorker(slot, name) {
|
|
4304
|
-
const worker = createImageWorker();
|
|
4305
|
-
if (!worker)
|
|
4306
|
-
return;
|
|
4307
|
-
slot.worker = worker;
|
|
4308
|
-
worker.on("message", (message) => this.handleWorkerMessage(slot, message));
|
|
4309
|
-
worker.once("error", (error) => {
|
|
4310
|
-
if (!this.destroyed)
|
|
4311
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4312
|
-
});
|
|
4313
|
-
worker.once("exit", (code) => {
|
|
4314
|
-
if (!this.destroyed)
|
|
4315
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
|
|
4316
|
-
});
|
|
4317
|
-
}
|
|
4318
|
-
renderInline(slot, request) {
|
|
4319
|
-
try {
|
|
4320
|
-
const result = renderImageVisualFrame(request);
|
|
4321
|
-
if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
|
|
4322
|
-
this.stageFrame({ key: result.key, sequence: result.sequence });
|
|
4323
|
-
this.onFrameReady?.();
|
|
4324
|
-
}
|
|
4325
|
-
} catch (error) {
|
|
4326
|
-
this.failWorker(String(request.layer ?? "visual"), error);
|
|
4327
|
-
}
|
|
4328
|
-
}
|
|
4329
|
-
enqueueWorkerRender(slot, request) {
|
|
4330
|
-
this.throwIfFatal();
|
|
4331
|
-
if (slot.busy) {
|
|
4332
|
-
slot.queuedRequest = request;
|
|
4333
|
-
slot.queuedKey = request.key;
|
|
4334
|
-
return;
|
|
4335
|
-
}
|
|
4336
|
-
this.dispatchWorkerRender(slot, request);
|
|
4337
|
-
}
|
|
4338
|
-
dispatchWorkerRender(slot, request) {
|
|
4339
|
-
this.throwIfFatal();
|
|
4340
|
-
if (!slot.worker)
|
|
4341
|
-
this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
|
|
4342
|
-
slot.busy = true;
|
|
4343
|
-
slot.inFlightKey = request.key;
|
|
4344
|
-
try {
|
|
4345
|
-
slot.worker.postMessage(request);
|
|
4346
|
-
} catch (error) {
|
|
4347
|
-
this.failWorker(String(request.layer ?? "visual"), error);
|
|
4348
|
-
}
|
|
4349
|
-
}
|
|
4350
|
-
handleWorkerMessage(slot, message) {
|
|
4351
|
-
if (this.destroyed || !message || typeof message !== "object")
|
|
4352
|
-
return;
|
|
4353
|
-
const response = message;
|
|
4354
|
-
slot.busy = false;
|
|
4355
|
-
slot.inFlightKey = "";
|
|
4356
|
-
const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
|
|
4357
|
-
if (!isStale) {
|
|
4358
|
-
if (response.error)
|
|
4359
|
-
this.failWorker(pendingLayerKey(response.key), new Error(response.error));
|
|
4360
|
-
if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
|
|
4361
|
-
this.stageFrame({ key: response.key, sequence: response.sequence });
|
|
4362
|
-
this.onFrameReady?.();
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
const next = slot.queuedRequest;
|
|
4366
|
-
slot.queuedRequest = null;
|
|
4367
|
-
slot.queuedKey = "";
|
|
4368
|
-
if (next && !this.isKnownKey(slot, next.key))
|
|
4369
|
-
this.dispatchWorkerRender(slot, next);
|
|
4370
|
-
}
|
|
3427
|
+
// packages/cli/src/app-opentui/render/text.ts
|
|
3428
|
+
var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
|
|
3429
|
+
var HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
|
|
3430
|
+
function lineIsClickable(line2) {
|
|
3431
|
+
return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
|
|
4371
3432
|
}
|
|
4372
|
-
function
|
|
4373
|
-
|
|
4374
|
-
const base = resolution?.width && resolution?.height ? resolution : { width: renderer.width * 12, height: renderer.height * 24 };
|
|
4375
|
-
const scale = imageResolutionScale();
|
|
4376
|
-
return {
|
|
4377
|
-
width: Math.max(renderer.width, Math.floor(base.width * scale)),
|
|
4378
|
-
height: Math.max(renderer.height, Math.floor(base.height * scale))
|
|
4379
|
-
};
|
|
3433
|
+
function lineSelectableId(line2) {
|
|
3434
|
+
return line2?.selectable?.id ?? line2?.selectableId;
|
|
4380
3435
|
}
|
|
4381
|
-
function
|
|
4382
|
-
|
|
3436
|
+
function lineBackground(line2) {
|
|
3437
|
+
if (line2?.bg)
|
|
3438
|
+
return RGBA4.fromHex(line2.bg);
|
|
3439
|
+
const id = lineSelectableId(line2);
|
|
3440
|
+
if (id !== undefined && isHovered(id))
|
|
3441
|
+
return HOVER_BG;
|
|
3442
|
+
return TRANSPARENT2;
|
|
4383
3443
|
}
|
|
4384
|
-
|
|
4385
|
-
// packages/cli/src/app-opentui/render/text.ts
|
|
4386
|
-
import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
|
|
4387
|
-
var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
|
|
4388
3444
|
function selectableMouseHandler(currentLineRef, onLineMouseDown) {
|
|
4389
3445
|
return (event) => {
|
|
4390
3446
|
const currentLine = currentLineRef();
|
|
4391
|
-
if (currentLine
|
|
3447
|
+
if (!lineIsClickable(currentLine))
|
|
4392
3448
|
return;
|
|
4393
3449
|
onLineMouseDown?.(currentLine, event);
|
|
4394
3450
|
};
|
|
4395
3451
|
}
|
|
3452
|
+
function hoverHandlers(currentLineRef) {
|
|
3453
|
+
return {
|
|
3454
|
+
onMouseOver: () => {
|
|
3455
|
+
const id = lineSelectableId(currentLineRef());
|
|
3456
|
+
if (id !== undefined)
|
|
3457
|
+
setHoveredSelectableId(id);
|
|
3458
|
+
},
|
|
3459
|
+
onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
4396
3462
|
function createTextLine(renderer, id, top, onLineMouseDown) {
|
|
4397
3463
|
let currentLine;
|
|
4398
3464
|
const renderable = new TextRenderable(renderer, {
|
|
@@ -4404,7 +3470,8 @@ function createTextLine(renderer, id, top, onLineMouseDown) {
|
|
|
4404
3470
|
width: 1,
|
|
4405
3471
|
fg: RIG_UI.ink2,
|
|
4406
3472
|
selectable: true,
|
|
4407
|
-
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
|
|
3473
|
+
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
|
|
3474
|
+
...hoverHandlers(() => currentLine)
|
|
4408
3475
|
});
|
|
4409
3476
|
renderable.setRigSceneLine = (line2) => {
|
|
4410
3477
|
currentLine = line2;
|
|
@@ -4421,7 +3488,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
|
|
|
4421
3488
|
flexShrink: 0,
|
|
4422
3489
|
fg: RIG_UI.ink2,
|
|
4423
3490
|
selectable: true,
|
|
4424
|
-
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
|
|
3491
|
+
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
|
|
3492
|
+
...hoverHandlers(() => currentLine)
|
|
4425
3493
|
});
|
|
4426
3494
|
renderable.setRigSceneLine = (line2) => {
|
|
4427
3495
|
currentLine = line2;
|
|
@@ -4435,7 +3503,7 @@ function applyTextLine(renderable, line2, top, left, width) {
|
|
|
4435
3503
|
renderable.width = width;
|
|
4436
3504
|
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
4437
3505
|
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
4438
|
-
renderable.bg = line2
|
|
3506
|
+
renderable.bg = lineBackground(line2);
|
|
4439
3507
|
renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4440
3508
|
}
|
|
4441
3509
|
function applyFlowTextLine(renderable, line2, width) {
|
|
@@ -4445,7 +3513,7 @@ function applyFlowTextLine(renderable, line2, width) {
|
|
|
4445
3513
|
renderable.width = Math.max(1, width);
|
|
4446
3514
|
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
4447
3515
|
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
4448
|
-
renderable.bg = line2
|
|
3516
|
+
renderable.bg = lineBackground(line2);
|
|
4449
3517
|
renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4450
3518
|
}
|
|
4451
3519
|
function clearTextLines(renderables, from = 0) {
|
|
@@ -5557,7 +4625,6 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5557
4625
|
const events = createAppEventBus();
|
|
5558
4626
|
let renderer = null;
|
|
5559
4627
|
let graphics = null;
|
|
5560
|
-
let imageVisuals = null;
|
|
5561
4628
|
let preloaderBackdrop = null;
|
|
5562
4629
|
const scrollPanels = [];
|
|
5563
4630
|
let typeBar = null;
|
|
@@ -5585,7 +4652,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5585
4652
|
if (!renderer || destroyed)
|
|
5586
4653
|
return;
|
|
5587
4654
|
const currentState = store.getState();
|
|
5588
|
-
renderedSelectableItems = renderNow(renderer, graphics,
|
|
4655
|
+
renderedSelectableItems = renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
|
|
5589
4656
|
focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
|
|
5590
4657
|
maybeRedirectFromUnresolvedScene(currentState);
|
|
5591
4658
|
};
|
|
@@ -5745,18 +4812,8 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5745
4812
|
});
|
|
5746
4813
|
renderer.setTerminalTitle?.("Operator cockpit");
|
|
5747
4814
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
const flushImageVisuals = async () => {
|
|
5751
|
-
imageVisuals?.flush();
|
|
5752
|
-
};
|
|
5753
|
-
renderer.setFrameCallback(flushImageVisuals);
|
|
5754
|
-
resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
|
|
5755
|
-
}
|
|
5756
|
-
if (!imageVisuals?.enabled) {
|
|
5757
|
-
graphics = createGraphicsLayer(renderer, layout);
|
|
5758
|
-
renderer.root.add(graphics);
|
|
5759
|
-
}
|
|
4815
|
+
graphics = createGraphicsLayer(renderer, layout);
|
|
4816
|
+
renderer.root.add(graphics);
|
|
5760
4817
|
preloaderBackdrop = new BoxRenderable3(renderer, {
|
|
5761
4818
|
id: "rig-preloader-backdrop",
|
|
5762
4819
|
position: "absolute",
|
|
@@ -5779,8 +4836,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5779
4836
|
} else if (!selectById(store, id, renderedSelectableItems)) {
|
|
5780
4837
|
return;
|
|
5781
4838
|
}
|
|
5782
|
-
|
|
5783
|
-
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
4839
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
5784
4840
|
};
|
|
5785
4841
|
for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
|
|
5786
4842
|
const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, onLineClick);
|
|
@@ -5847,6 +4903,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5847
4903
|
};
|
|
5848
4904
|
renderer.keyInput.on("keypress", onKeyPress);
|
|
5849
4905
|
resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
|
|
4906
|
+
resources.add(subscribeHover(() => scheduleAppRender(0)));
|
|
5850
4907
|
const onPaste = (event) => {
|
|
5851
4908
|
const state = store.getState();
|
|
5852
4909
|
if (state.scene === "handoff") {
|
|
@@ -5882,7 +4939,6 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5882
4939
|
stopActivePiHost("renderer destroyed");
|
|
5883
4940
|
stopActiveCommandHost("renderer destroyed");
|
|
5884
4941
|
destroyNativeHost(nativeHost);
|
|
5885
|
-
imageVisuals?.destroy();
|
|
5886
4942
|
if (renderTimer)
|
|
5887
4943
|
clearTimeout(renderTimer);
|
|
5888
4944
|
renderTimer = null;
|
|
@@ -5931,14 +4987,13 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5931
4987
|
clearTimeout(renderTimer);
|
|
5932
4988
|
renderTimer = null;
|
|
5933
4989
|
resources.disposeAll();
|
|
5934
|
-
imageVisuals?.destroy();
|
|
5935
4990
|
stopActivePiHost("app shutdown");
|
|
5936
4991
|
stopActiveCommandHost("app shutdown");
|
|
5937
4992
|
if (renderer && !renderer.isDestroyed)
|
|
5938
4993
|
renderer.destroy();
|
|
5939
4994
|
}
|
|
5940
4995
|
}
|
|
5941
|
-
function renderNow(renderer, graphics,
|
|
4996
|
+
function renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
|
|
5942
4997
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
5943
4998
|
const hideNav = () => {
|
|
5944
4999
|
if (navText)
|
|
@@ -5947,7 +5002,6 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5947
5002
|
if (layout.tooSmall) {
|
|
5948
5003
|
if (graphics)
|
|
5949
5004
|
clearGraphicsLayer(graphics);
|
|
5950
|
-
imageVisuals?.clear();
|
|
5951
5005
|
if (preloaderBackdrop)
|
|
5952
5006
|
preloaderBackdrop.visible = false;
|
|
5953
5007
|
if (brandText)
|
|
@@ -5981,8 +5035,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5981
5035
|
preloaderTransition.minUntilMs = now;
|
|
5982
5036
|
const targetFrame = renderState.typeBar.mode === "command" ? commandPaletteFrame(renderState, layout) : rendererForScene(renderState.scene, sceneRendererRegistry)(renderState, layout);
|
|
5983
5037
|
const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
|
|
5984
|
-
const
|
|
5985
|
-
let targetVisualReady = true;
|
|
5038
|
+
const targetVisualReady = true;
|
|
5986
5039
|
const fleetStats = fleetStatsFromState(state);
|
|
5987
5040
|
const fleetLoad = fleetStats.load;
|
|
5988
5041
|
const fleetActive = fleetStats.active;
|
|
@@ -5992,23 +5045,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5992
5045
|
if (renderState.scene === "command") {
|
|
5993
5046
|
getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
|
|
5994
5047
|
}
|
|
5995
|
-
if (imageVisuals && targetFrame.terminalActive) {
|
|
5996
|
-
imageVisuals.clear();
|
|
5997
|
-
} else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
|
|
5998
|
-
const visualInput = {
|
|
5999
|
-
layout: targetContentLayout,
|
|
6000
|
-
pixelSize: pixelSizeForRenderer(renderer),
|
|
6001
|
-
panels: targetPanelsForImages,
|
|
6002
|
-
scene: state.scene,
|
|
6003
|
-
tick: state.tick,
|
|
6004
|
-
load: fleetLoad,
|
|
6005
|
-
activeRuns: fleetActive
|
|
6006
|
-
};
|
|
6007
|
-
imageVisuals.render(visualInput);
|
|
6008
|
-
targetVisualReady = imageVisuals.isReady(visualInput);
|
|
6009
|
-
}
|
|
6010
5048
|
const preloaderActive = sceneJustChanged && !cachedFastPath || isPreloaderActive(preloaderTransition, targetVisualReady, now);
|
|
6011
|
-
imageVisuals?.setFlushPaused(preloaderActive);
|
|
6012
5049
|
const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
|
|
6013
5050
|
if (graphics) {
|
|
6014
5051
|
resizeGraphicsLayer(graphics, layout);
|