159 lines
5.3 KiB
Svelte
159 lines
5.3 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
|
|
let {
|
|
layout = { type: 'pane', id: 0 },
|
|
activePaneId = 0
|
|
} = $props();
|
|
|
|
let canvas: HTMLCanvasElement;
|
|
let ctx: CanvasRenderingContext2D | null = null;
|
|
let containerWidth = $state(0);
|
|
let containerHeight = $state(0);
|
|
let animationFrameId: number;
|
|
let lastTime = 0;
|
|
let cursorBlink = true;
|
|
|
|
// Colors
|
|
const BG_COLOR = '#0a0a0a';
|
|
const BORDER_COLOR = '#333333';
|
|
const ACTIVE_BORDER_COLOR = '#00ff00';
|
|
const TEXT_COLOR = '#00ff00';
|
|
const MUTED_TEXT = '#444444';
|
|
|
|
$effect(() => {
|
|
if (canvas && layout) {
|
|
draw();
|
|
}
|
|
});
|
|
|
|
function resize() {
|
|
if (!canvas) return;
|
|
const parent = canvas.parentElement;
|
|
if (parent) {
|
|
// Handle high DPI displays
|
|
const dpr = window.devicePixelRatio || 1;
|
|
const rect = parent.getBoundingClientRect();
|
|
|
|
canvas.width = rect.width * dpr;
|
|
canvas.height = rect.height * dpr;
|
|
|
|
// Scale context to match
|
|
ctx?.scale(dpr, dpr);
|
|
|
|
containerWidth.set(rect.width);
|
|
containerHeight.set(rect.height);
|
|
|
|
draw();
|
|
}
|
|
}
|
|
|
|
function animate(time: number) {
|
|
if (time - lastTime > 500) { // Blink every 500ms
|
|
cursorBlink = !cursorBlink;
|
|
lastTime = time;
|
|
draw();
|
|
}
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
}
|
|
|
|
onMount(() => {
|
|
ctx = canvas.getContext('2d');
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', resize);
|
|
cancelAnimationFrame(animationFrameId);
|
|
};
|
|
});
|
|
|
|
function draw() {
|
|
if (!ctx) return;
|
|
|
|
// Clear
|
|
ctx.fillStyle = BG_COLOR;
|
|
ctx.fillRect(0, 0, containerWidth.get(), containerHeight.get());
|
|
|
|
// Draw Layout
|
|
drawNode(layout, 0, 0, containerWidth.get(), containerHeight.get());
|
|
}
|
|
|
|
function drawNode(node: any, x: number, y: number, w: number, h: number) {
|
|
if (!ctx) return;
|
|
|
|
// Add a small gap for the border
|
|
const gap = 2;
|
|
|
|
if (node.type === 'pane') {
|
|
// Draw Pane Background
|
|
ctx.fillStyle = '#050505';
|
|
ctx.fillRect(x + gap, y + gap, w - gap*2, h - gap*2);
|
|
|
|
// Draw Border
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = node.id === activePaneId ? ACTIVE_BORDER_COLOR : BORDER_COLOR;
|
|
|
|
// If active, make border slightly thicker/glowy
|
|
if (node.id === activePaneId) {
|
|
ctx.shadowColor = ACTIVE_BORDER_COLOR;
|
|
ctx.shadowBlur = 4;
|
|
} else {
|
|
ctx.shadowBlur = 0;
|
|
}
|
|
|
|
ctx.strokeRect(x + gap, y + gap, w - gap*2, h - gap*2);
|
|
ctx.shadowBlur = 0; // Reset
|
|
|
|
// Draw Pane ID/Status
|
|
ctx.font = '12px "JetBrains Mono", monospace';
|
|
ctx.fillStyle = node.id === activePaneId ? TEXT_COLOR : MUTED_TEXT;
|
|
ctx.fillText(`[${node.id}] zsh`, x + 15, y + 25);
|
|
|
|
// Draw "Content" simulation
|
|
if (h > 60) {
|
|
ctx.fillStyle = MUTED_TEXT;
|
|
const lines = Math.floor((h - 50) / 16);
|
|
for (let i = 0; i < Math.min(lines, 8); i++) {
|
|
// Randomize line length to look like code
|
|
const width = (Math.sin(i * 132 + node.id) * 0.5 + 0.5) * (w - 60) + 20;
|
|
ctx.fillRect(x + 15, y + 45 + (i * 16), width, 6);
|
|
}
|
|
|
|
// Cursor
|
|
if (node.id === activePaneId && cursorBlink) {
|
|
ctx.fillStyle = TEXT_COLOR;
|
|
const lastLineY = y + 45 + (Math.min(lines, 8) * 16);
|
|
ctx.fillRect(x + 15, lastLineY, 8, 14);
|
|
}
|
|
}
|
|
|
|
} else if (node.type === 'split-v') {
|
|
// Vertical Split (Top/Bottom)
|
|
const h1 = h * (node.ratio || 0.5);
|
|
const h2 = h - h1;
|
|
drawNode(node.children[0], x, y, w, h1);
|
|
drawNode(node.children[1], x, y + h1, w, h2);
|
|
} else if (node.type === 'split-h') {
|
|
// Horizontal Split (Left/Right)
|
|
const w1 = w * (node.ratio || 0.5);
|
|
const w2 = w - w1;
|
|
drawNode(node.children[0], x, y, w1, h);
|
|
drawNode(node.children[1], x + w1, y, w2, h);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="w-full h-full min-h-[400px] bg-black border border-border relative group overflow-hidden">
|
|
<canvas bind:this={canvas} class="block w-full h-full"></canvas>
|
|
|
|
<!-- Scanline overlay for the canvas specifically -->
|
|
<div class="absolute inset-0 pointer-events-none opacity-10 bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%]"></div>
|
|
|
|
<div class="absolute bottom-2 right-2 text-[10px] text-muted-foreground opacity-50 group-hover:opacity-100 transition-opacity z-20 font-mono">
|
|
RENDERER: CANVAS_2D // {containerWidth.get()}x{containerHeight.get()}
|
|
</div>
|
|
</div>
|