Skip to main content

kronos_tide/
renderer.rs

1use crate::multiplexer::Multiplexer;
2use crate::pane::Pane;
3use crossterm::{cursor, style, terminal, QueueableCommand};
4use std::io::{self, Write};
5
6pub struct Renderer {
7    stdout: io::Stdout,
8}
9
10impl Renderer {
11    pub fn new() -> Self {
12        Self {
13            stdout: io::stdout(),
14        }
15    }
16
17    pub fn render_pane(&self, pane: &Pane, is_active: bool) -> io::Result<()> {
18        let mut stdout = io::stdout();
19        let buf = pane.buffer.lock().unwrap();
20        let (rows, cols) = buf.dimensions();
21
22        for row in 0..rows.min(pane.height as usize) {
23            stdout.queue(cursor::MoveTo(pane.x, pane.y + row as u16))?;
24            for col in 0..cols.min(pane.width as usize) {
25                let cell = buf.cell(row, col);
26                // Skip the right half of wide (width-2) characters — the left
27                // half already advanced the terminal cursor by 2 columns.
28                if cell.wide_continuation {
29                    continue;
30                }
31                stdout.queue(style::SetForegroundColor(style::Color::Rgb {
32                    r: cell.fg.r,
33                    g: cell.fg.g,
34                    b: cell.fg.b,
35                }))?;
36                stdout.queue(style::SetBackgroundColor(style::Color::Rgb {
37                    r: cell.bg.r,
38                    g: cell.bg.g,
39                    b: cell.bg.b,
40                }))?;
41                if cell.bold {
42                    stdout.queue(style::SetAttribute(style::Attribute::Bold))?;
43                }
44                // Print the base character followed by any combining marks.
45                stdout.queue(style::Print(cell.ch))?;
46                for &combining_ch in &cell.combining {
47                    stdout.queue(style::Print(combining_ch))?;
48                }
49                if cell.bold {
50                    stdout.queue(style::SetAttribute(style::Attribute::NoBold))?;
51                }
52            }
53        }
54
55        let (cursor_row, cursor_col) = buf.cursor();
56        if is_active {
57            stdout.queue(cursor::MoveTo(
58                pane.x + cursor_col as u16,
59                pane.y + cursor_row as u16,
60            ))?;
61            stdout.queue(cursor::Show)?;
62        }
63
64        stdout.queue(style::ResetColor)?;
65
66        Ok(())
67    }
68
69    pub fn render_status_bar(&self, mux: &Multiplexer) -> io::Result<()> {
70        let mut stdout = io::stdout();
71        let (cols, rows) = terminal::size()?;
72
73        stdout.queue(cursor::MoveTo(0, rows - 1))?;
74        stdout.queue(style::SetForegroundColor(style::Color::Rgb {
75            r: 200,
76            g: 200,
77            b: 200,
78        }))?;
79        stdout.queue(style::SetBackgroundColor(style::Color::Rgb {
80            r: 30,
81            g: 30,
82            b: 60,
83        }))?;
84
85        let prefix_indicator = if mux.is_prefix_mode() {
86            " [PREFIX] "
87        } else {
88            ""
89        };
90
91        let status = format!(
92            " KRONOS {} | {} | panes:{} | Ctrl+B=prefix{}",
93            env!("CARGO_PKG_VERSION"),
94            mux.active_pane_title(),
95            mux.pane_count(),
96            prefix_indicator,
97        );
98
99        let padded = format!("{:<width$}", status, width = cols as usize);
100        stdout.queue(style::Print(&padded[..padded.len().min(cols as usize)]))?;
101        stdout.queue(style::ResetColor)?;
102
103        Ok(())
104    }
105
106    pub fn flush(&mut self) -> io::Result<()> {
107        self.stdout.flush()
108    }
109}