use std::{fmt::Display, io::stdout}; use crate::{ layout::Allocation, unit::{Point, Rect}, widget::Widget, }; pub enum Instr { DrawText(Point, String), } pub struct RenderScope { pub rect: Rect, draw_instructions: Vec, } struct RenderTextResult { text: String, line_len: u16, lines_len: u16, } impl RenderTextResult { fn insert_newline(&mut self, height: u16) -> bool { if self.lines_len >= height { return true; } self.text.push('\n'); self.line_len = 0; self.lines_len += 1; false } fn insert(&mut self, c: char) { self.text.push(c); self.line_len += 1; } } impl RenderScope { pub fn draw_text, T: Display>(&mut self, at: P, text: T) -> u16 { if self.rect.width == 0 || self.rect.height == 0 { return 0; } let point: Point = at.into(); let text = text.to_string(); let mut chars = text.chars().peekable(); let mut result = RenderTextResult { text: String::new(), line_len: 0, lines_len: 1, }; while let Some(c) = chars.next() { if c == '\x1b' && chars.peek() == Some(&'[') { result.text.push(c); while let Some(c) = chars.next() { result.text.push(c); if c.is_ascii_alphabetic() { break; } } } else if c == '\n' { if result.insert_newline(self.rect.height) { break; } } else { result.insert(c); if result.line_len >= self.rect.width { if result.insert_newline(self.rect.height) { break; } } } } self.draw_instructions .push(Instr::DrawText(point, result.text)); result.lines_len } } impl RenderScope { pub fn new(rect: Rect) -> Self { Self { rect, draw_instructions: Vec::new(), } } } impl From<(u16, u16)> for Point { fn from(value: (u16, u16)) -> Self { Self { x: value.0, y: value.1, } } } impl From for Point { fn from(value: u16) -> Self { Self { x: value, y: value } } } impl From for Point { fn from(value: Rect) -> Self { Self { x: value.x, y: value.y, } } } impl Allocation { pub fn draw(&self, index: usize, widget: W) { let mut scope = RenderScope::new(self.widgets[index]); widget.render(&mut scope); scope.draw(); } pub fn draw_frame(&self, index: usize, widget: W) { let mut scope = RenderScope::new(self.frame[index]); widget.render(&mut scope); scope.draw(); } } impl RenderScope { pub fn draw(self) { for inst in self.draw_instructions { match inst { Instr::DrawText(point, text) => { for (i, line) in text.lines().enumerate() { if i as u16 + point.y >= self.rect.height { continue; } print_at( self.rect.x + point.x, self.rect.y + point.y + i as u16, line, ); } } } } } } pub fn print_at(x: u16, y: u16, text: &str) { crossterm::execute!(stdout(), crossterm::cursor::MoveTo(x, y)).unwrap(); print!("{text}"); }