shellshot 0.5.0

Transform your command-line output into clean, shareable images with a single command.
Documentation
use image::Rgba;
use termwiz::cell::Cell;

use crate::{
    image_renderer::{
        ImageRendererError,
        canvas::{Canvas, Corners},
        render_size::Size,
        utils::{darken_color, lighten_color},
    },
    theme::Theme,
    window_decoration::{
        Fonts, WindowMetrics,
        common::{default_build_command_line, default_font},
    },
};

use super::WindowDecoration;

#[derive(Debug)]
pub struct Classic;

const GREEN: Rgba<u8> = Rgba([52, 199, 89, 255]);
const YELLOW: Rgba<u8> = Rgba([255, 189, 45, 255]);
const RED: Rgba<u8> = Rgba([255, 95, 87, 255]);

impl WindowDecoration for Classic {
    fn build_command_line(&self, command: &str) -> Vec<Cell> {
        default_build_command_line(command)
    }

    fn compute_metrics(&self, char_size: Size) -> WindowMetrics {
        let char_height = char_size.height;

        let padding = char_height;
        let border_width = 1;
        let title_bar_height = char_height;

        WindowMetrics {
            padding,
            border_width,
            title_bar_height,
        }
    }

    fn font(&self) -> Result<Fonts, ImageRendererError> {
        default_font()
    }

    fn draw_window(
        &self,
        canvas: &mut Canvas,
        metrics: &WindowMetrics,
        theme: &Theme,
    ) -> Result<(), ImageRendererError> {
        draw_window_decorations(canvas, metrics, theme)
    }
}

fn draw_window_decorations(
    canvas: &mut Canvas,
    metrics: &WindowMetrics,
    theme: &Theme,
) -> Result<(), ImageRendererError> {
    let bg_color = theme.background_color;
    let title_bar_color = darken_color(bg_color, 0.2); // 20% darker than background
    let border_color = lighten_color(bg_color, 0.2); // 20% lighter than background

    canvas.fill_rounded(
        border_color,
        metrics.title_bar_height as f32 / 4.0,
        &Corners::ALL,
    );

    canvas.fill_rounded_rect(
        i32::try_from(metrics.border_width)?,
        i32::try_from(metrics.border_width)?,
        canvas.width() - 2 * metrics.border_width,
        canvas.height() - 2 * metrics.border_width,
        bg_color,
        metrics.title_bar_height as f32 / 4.0,
        &Corners::ALL,
    );

    canvas.fill_rounded_rect(
        i32::try_from(metrics.border_width)?,
        i32::try_from(metrics.border_width)?,
        canvas.width() - 2 * metrics.border_width,
        metrics.title_bar_height,
        title_bar_color,
        metrics.title_bar_height as f32 / 4.0,
        &(Corners::TOP_LEFT | Corners::TOP_RIGHT),
    );

    draw_window_buttons(canvas, metrics)
}

fn draw_window_buttons(
    canvas: &mut Canvas,
    metrics: &WindowMetrics,
) -> Result<(), ImageRendererError> {
    let btn_y = i32::try_from(metrics.border_width + (metrics.title_bar_height / 2))?;
    let radius = i32::try_from(metrics.title_bar_height / 4)?;
    let spacing = radius * 3;

    let right = i32::try_from(canvas.width() - metrics.border_width)?;

    canvas.fill_circle(right - spacing, btn_y, radius, RED);

    canvas.fill_circle(right - 2 * spacing, btn_y, radius, YELLOW);

    canvas.fill_circle(right - 3 * spacing, btn_y, radius, GREEN);

    Ok(())
}