cc-switch-tui 0.1.3

All-in-One Assistant for Claude Code, Codex, Gemini, OpenCode, OpenClaw & Hermes
Documentation
use super::super::theme;
use super::super::*;

pub(crate) fn compact_message_overlay_rect(content_area: Rect, title: &str, message: &str) -> Rect {
    compact_lines_overlay_rect(content_area, title, &[message.to_string()])
}

pub(crate) fn should_use_compact_lines_overlay(
    content_area: Rect,
    title: &str,
    lines: &[String],
) -> bool {
    if lines.is_empty() || lines.len() > 8 {
        return false;
    }

    let area = compact_lines_overlay_rect(content_area, title, lines);
    area.width < content_area.width.saturating_sub(6) && area.height <= 12
}

pub(crate) fn compact_lines_overlay_rect(
    content_area: Rect,
    title: &str,
    lines: &[String],
) -> Rect {
    let max_width = content_area
        .width
        .saturating_sub(4)
        .max(1)
        .min(TOAST_MAX_WIDTH);
    let min_width = 36.min(max_width);
    let content_width = lines
        .iter()
        .map(|line| UnicodeWidthStr::width(line.as_str()))
        .max()
        .unwrap_or(0)
        .max(UnicodeWidthStr::width(title)) as u16;
    let width = content_width.saturating_add(8).clamp(min_width, max_width);

    let inner_width = width.saturating_sub(2).max(1);
    let wrapped_height = lines
        .iter()
        .map(|line| wrap_message_lines(line, inner_width).len().max(1) as u16)
        .sum::<u16>()
        .max(1);
    let max_height = content_area.height.saturating_sub(4).max(1);
    let height = wrapped_height.saturating_add(3).max(6).min(max_height);

    centered_rect_fixed(width, height, content_area)
}

pub(crate) fn centered_text_lines(lines: &[String], width: u16, height: u16) -> Vec<Line<'static>> {
    let mut wrapped = Vec::new();
    for line in lines {
        wrapped.extend(wrap_message_lines(line, width));
    }
    if wrapped.is_empty() {
        wrapped.push(String::new());
    }

    let pad = height.saturating_sub(wrapped.len() as u16) / 2;
    let mut out = Vec::with_capacity(pad as usize + wrapped.len());
    for _ in 0..pad {
        out.push(Line::raw(""));
    }
    out.extend(wrapped.into_iter().map(Line::raw));
    out
}

pub(crate) fn content_pane_rect(area: Rect, theme: &theme::Theme) -> Rect {
    let root = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3),
            Constraint::Min(0),
            Constraint::Length(1),
        ])
        .split(area);

    let body = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Length(nav_pane_width(theme)),
            Constraint::Min(0),
        ])
        .split(root[1]);

    body[1]
}

pub(crate) fn centered_message_lines(message: &str, width: u16, height: u16) -> Vec<Line<'static>> {
    let lines = wrap_message_lines(message, width);
    let pad = height.saturating_sub(lines.len() as u16) / 2;
    let mut out = Vec::with_capacity(pad as usize + lines.len());
    for _ in 0..pad {
        out.push(Line::raw(""));
    }
    out.extend(lines.into_iter().map(Line::raw));
    out
}

pub(crate) fn wrap_message_lines(message: &str, width: u16) -> Vec<String> {
    let width = width as usize;
    if width == 0 {
        return vec![String::new()];
    }

    let mut lines = Vec::new();
    let mut current = String::new();
    let mut current_width = 0usize;

    for ch in message.chars() {
        if ch == '\n' {
            lines.push(current);
            current = String::new();
            current_width = 0;
            continue;
        }

        let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0).max(1);
        if current_width + ch_width > width && !current.is_empty() {
            lines.push(current);
            current = String::new();
            current_width = 0;
        }

        current.push(ch);
        current_width = current_width.saturating_add(ch_width);
    }

    if !current.is_empty() || lines.is_empty() {
        lines.push(current);
    }

    lines
}

pub(crate) fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - percent_y) / 2),
            Constraint::Percentage(percent_y),
            Constraint::Percentage((100 - percent_y) / 2),
        ])
        .split(r);

    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - percent_x) / 2),
            Constraint::Percentage(percent_x),
            Constraint::Percentage((100 - percent_x) / 2),
        ])
        .split(popup_layout[1])[1]
}

pub(crate) fn centered_rect_fixed(width: u16, height: u16, r: Rect) -> Rect {
    let width = width.min(r.width);
    let height = height.min(r.height);

    Rect {
        x: r.x + r.width.saturating_sub(width) / 2,
        y: r.y + r.height.saturating_sub(height) / 2,
        width,
        height,
    }
}