Skip to main content

beyonder_gpu/block_renderers/
shell_block.rs

1//! Renderer for ShellCommand blocks.
2//! Two-rect layout: [cmd bar] gap [output panel]
3//! The command bar and output panel are visually distinct — different backgrounds,
4//! a clear gap between them — so `$ ls` and the directory listing look separate.
5
6use beyonder_core::{Block, BlockContent, BlockKind, BlockStatus};
7
8use crate::pipeline::RectInstance;
9
10#[allow(clippy::too_many_arguments)]
11pub fn render_shell_block(
12    block: &Block,
13    x: f32,
14    y: f32,
15    width: f32,
16    height: f32,
17    font_size: f32,
18    scale: f32,
19    rects: &mut Vec<RectInstance>,
20) {
21    let BlockContent::ShellCommand {
22        output, exit_code, ..
23    } = &block.content
24    else {
25        return;
26    };
27
28    let cmd_bar_h = font_size * 2.8;
29    let inner_gap = font_size * 0.4;
30
31    let last_content = output
32        .rows
33        .iter()
34        .rposition(|row| {
35            row.cells.iter().any(|c| {
36                let fc = c.grapheme.chars().next().unwrap_or('\0');
37                fc != ' ' && fc != '\0'
38            })
39        })
40        .map(|i| i + 1)
41        .unwrap_or(0);
42    let has_output = last_content > 0;
43
44    // --- Cmd bar (Rect 1) ---
45    // Flat terminal background — no tint.
46    rects.push(
47        RectInstance::filled(x, y, width, cmd_bar_h, [0.118, 0.118, 0.180, 1.0])
48            .with_radius(3.0 * scale),
49    );
50
51    // Left accent stripe (kind-colored) — replaces the running mauve stripe when done.
52    let accent = match block.kind {
53        BlockKind::Human => [0.271, 0.278, 0.353, 0.7],
54        BlockKind::Agent => [0.537, 0.706, 0.980, 0.85],
55        BlockKind::Approval => [0.976, 0.886, 0.686, 0.90],
56        BlockKind::System => [0.271, 0.278, 0.353, 0.4],
57        BlockKind::Tool => [0.580, 0.886, 0.835, 0.75],
58    };
59    let stripe_w = 3.0 * scale;
60    rects.push(RectInstance::filled(x, y, stripe_w, cmd_bar_h, accent).with_radius(scale));
61
62    // Running indicator — Mauve pulse strip (overrides accent when actively running).
63    if block.status == BlockStatus::Running {
64        rects.push(
65            RectInstance::filled(x, y, stripe_w, cmd_bar_h, [0.796, 0.651, 0.969, 0.95]) // Mauve #cba6f7
66                .with_radius(scale),
67        );
68    }
69
70    // Exit-code right-edge indicator on the cmd bar.
71    if let Some(code) = exit_code {
72        let indicator = if *code == 0 {
73            [0.651, 0.890, 0.631, 0.80] // Green #a6e3a1 — success
74        } else {
75            [0.953, 0.545, 0.659, 0.85] // Red #f38ba8 — non-zero exit
76        };
77        rects.push(
78            RectInstance::filled(x + width - stripe_w, y, stripe_w, cmd_bar_h, indicator)
79                .with_radius(scale),
80        );
81    }
82
83    // --- Output panel (Rect 2) — only when there's output or command is still running ---
84    if has_output || block.status == BlockStatus::Running {
85        let out_y = y + cmd_bar_h + inner_gap;
86        let out_h = height - cmd_bar_h - inner_gap;
87        if out_h > 1.0 {
88            // Output panel — same flat terminal background.
89            rects.push(
90                RectInstance::filled(x, out_y, width, out_h, [0.118, 0.118, 0.180, 1.0])
91                    .with_radius(3.0 * scale),
92            );
93        }
94    }
95}