167 lines
3.7 KiB
Rust
167 lines
3.7 KiB
Rust
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<Instr>,
|
|
}
|
|
|
|
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<P: Into<Point>, 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<u16> for Point {
|
|
fn from(value: u16) -> Self {
|
|
Self { x: value, y: value }
|
|
}
|
|
}
|
|
|
|
impl From<Rect> for Point {
|
|
fn from(value: Rect) -> Self {
|
|
Self {
|
|
x: value.x,
|
|
y: value.y,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Allocation {
|
|
pub fn draw<W: Widget>(&self, index: usize, widget: W) {
|
|
let mut scope = RenderScope::new(self.widgets[index]);
|
|
widget.render(&mut scope);
|
|
scope.draw();
|
|
}
|
|
|
|
pub fn draw_frame<W: Widget>(&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}");
|
|
}
|