use std::clone::Clone; use std::fmt::Write; use std::vec; use ttf_parser::{Face, OutlineBuilder, Rect}; use vte::{Params, Perform}; use crate::config::FONT; // public structs pub struct Model { screenbuffer: Buffer, buffer: Buffer, cell: Rect, cursor: Cursor, } impl Model { // width and height are measured in cells pub fn new(width: usize, height: usize, scale: f32) -> Self { let face = Face::parse(FONT, 0).unwrap(); let cell = Rect { x_min: 0, y_min: 0, x_max: face.height(), y_max: face.height(), }; Model { screenbuffer: Buffer::new( 0, (cell.width() as f32 * width as f32 * scale) as usize, (cell.height() as f32 * height as f32 * scale) as usize, ), buffer: Buffer::new(Cell::new(None), width, height), cell: cell, cursor: Cursor::new(), } } // helper pub fn scale(&self) -> f32 { self.screenbuffer.height as f32 / (self.cell.height() as f32 * self.buffer.height as f32) } // concerning screenbuffer pub fn screenbuffer_width(&self) -> usize { self.screenbuffer.width } pub fn screenbuffer_height(&self) -> usize { self.screenbuffer.height } pub fn screenbuffer_buffer(&self) -> &Vec { &self.screenbuffer.buffer } // sets screenbuffer to the contents of `buf`, ignoring extra pub fn set_screenbuffer(&mut self, buf: &[u8]) { for (p, m) in self.screenbuffer.buffer.iter_mut().zip(buf.iter()) { let m0 = *m as u32; *p = m0 << 16 | m0 << 8 | m0; } } // concerning buffer pub fn buffer_width(&self) -> usize { self.buffer.width } pub fn buffer_height(&self) -> usize { self.buffer.height } // WARNING: 1-indexed pub fn buffer_at(&self, row: usize, col: usize) -> Cell { self.buffer.get(col - 1, row - 1) } // WARNING: 1-indexed fn set_buffer_at(&mut self, row: usize, col: usize, val: Cell) { self.buffer.set(col - 1, row - 1, val); } // concerning cell pub fn cell_width(&self) -> i16 { self.cell.width() } pub fn cell_height(&self) -> i16 { self.cell.height() } // concerning cursor fn set_cursor_row(&mut self, row: usize) { self.cursor.row = row; if row < 1 { self.cursor.row = 1; } // notice no lower bounds check; should automatically scroll in print func } fn set_cursor_col(&mut self, col: usize) { self.cursor.col = col; if col < 1 { self.cursor.col = 1; } if col > self.buffer.width{ self.cursor.col = self.buffer.width; } } fn set_cursor(&mut self, row: usize, col: usize) { self.set_cursor_row(row); self.set_cursor_col(col); } fn dec_cursor_row(&mut self, row: usize) { self.set_cursor_row(self.cursor.row - row); } fn dec_cursor_col(&mut self, col: usize) { self.set_cursor_col(self.cursor.col - col); } fn inc_cursor_row(&mut self, row: usize) { self.set_cursor_row(self.cursor.row + row); } fn inc_cursor_col(&mut self, col: usize) { self.set_cursor_col(self.cursor.col + col); } fn set_cursor_char_attr(&mut self, char_attr: CharAttr) { self.cursor.char_attr = char_attr; } } impl Perform for Model { // draw a character to the screen and update states fn print(&mut self, c: char) { println!("[print] {:?}", c); // cycle everything up a line until the cursor is on screen while self.cursor.row > self.buffer.height { println!("cursor off screen, fixing"); for row in 1..self.buffer.height { for col in 1..=self.buffer.width { self.set_buffer_at(row, col, self.buffer_at(row + 1, col)); } } for col in 1..=self.buffer.width { self.set_buffer_at(self.buffer.height, col, Cell::new(None)); } self.dec_cursor_row(1); } // draw to the cursor position self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new_with(Some(c), self.cursor.char_attr)); // move the cursor further to the right self.inc_cursor_col(1); } // execute a C0 or C1 control function fn execute(&mut self, byte: u8) { let mut recognised = true; match byte { // TODO seen in the wild: // 0x07 0x00 => (), // BEL 0x07 => (), // BS 0x08 => { self.dec_cursor_col(1); self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new(None)); }, // LF 0x0A => self.inc_cursor_row(1), // CR 0x0D => self.set_cursor_col(1), // DEL 0x0F => { self.inc_cursor_col(1); self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new(None)); } _ => recognised = false, } print!("[execute] "); if recognised == false { print!("UNIMPLEMENTED ") }; println!("{:02x}", byte); } // invoked when a final character arrives in first part of device control string fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } // pass bytes as part of a device control string to the handle chosen by hook // C0 controls are also passed to this handler fn put(&mut self, byte: u8) { println!("[put] {:02x}", byte); } // called when a device control string is terminated fn unhook(&mut self) { println!("[unhook]"); } // dispatch an operating system command fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { println!( "[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated ); } // a final character has arrived for a csi sequence fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { let mut recognised = true; match (params, intermediates, ignore, c) { // comments come from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html // TODO seen in the wild: // CSI ? 200* h // xterm stuff // CSI ? 200* l // xterm stuff // // priority: // CSI Ps ; Ps f // dk // CSI ? Ps h // random magic number codes // CSI ? Ps l // likewise // CSI Ps ; Ps r // set scrolling region // TODO CSI Ps SP A // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). (_, _, false, 'A') => self.dec_cursor_row(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }), // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD) (_, _, false, 'B') => self.inc_cursor_row(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }), // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). (_, _, false, 'C') => self.inc_cursor_col(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }), // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). (_, _, false, 'D') => self.dec_cursor_col(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }), // CSI Ps ; Ps H // Cursor Position [row;column] (default [1,1]) (CUP) (_, _, false, 'H') => { let mut iter = params.iter(); self.set_cursor( if let Some(row) = iter.next() { row[0] as usize } else { 1 }, if let Some(col) = iter.next() { col[0] as usize } else { 1 }, ); }, // TODO Ps = {1,2,3} // CSI Ps J // Erase in Display (ED), VT100. // Ps = 0 => Erase Below (default). // Ps = 1 => Erase Above. // Ps = 2 => Erase All. // Ps = 3 => Erase Saved Lines, xterm. (_, _, false, 'J') => { // TODO inline from (_, _, false, 'K') case? // deletes line from cursor to right for col in self.cursor.col..=self.buffer.width { self.set_buffer_at(self.cursor.row, col, Cell::new(None)); }; // deletes every row below the cursor for row in (self.cursor.row + 1)..=self.buffer.height { for col in 1..=self.buffer.width { self.set_buffer_at(row, col, Cell::new(None)); }; }; }, // TODO Ps = {1,2} // CSI Ps K // Erase in Line (EL), VT100. // Ps = 0 => Erase to Right (default). // Ps = 1 => Erase to Left. // Ps = 2 => Erase All. (_, _, false, 'K') => { for col in self.cursor.col..=self.buffer.width { self.set_buffer_at(self.cursor.row, col, Cell::new(None)); }; }, // CSI // TODO a *lot*, color lives here // CSI Pm m // Character Attributes (SGR) // Ps = 0 => Normal // Ps = 1 => Bold // Ps = 4 => Underlined // Ps = 5 => Blink // Ps = 7 => Inverse (_, _, false, 'm') => { let mut iter = params.iter(); while let Some(value) = iter.next() { self.set_cursor_char_attr( match value[0] { 0 => CharAttr::Normal, 1 => CharAttr::Bold, 4 => CharAttr::Underlined, 5 => CharAttr::Blink, 7 => CharAttr::Inverse, _ => self.cursor.char_attr, } ) } println!("char_attr: {:?}", self.cursor.char_attr); }, _ => recognised = false, } print!("[csi_dispatch] "); if recognised == false { print!("UNIMPLEMENTED ") }; println!( "params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); println!("cursor at {:?};{:?}", self.cursor.row, self.cursor.col); } // the final character of an escape sequence has arrived fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { // seen in the wild: // in bt ct // 41 30 // 3D DECPAM // 3E DECPNM // 40 42 println!( "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", intermediates, ignore, byte ); } } // yoinked from tty_parser docs pub struct Builder(pub String); impl Builder { pub fn new() -> Self { Builder(String::new()) } } impl OutlineBuilder for Builder { fn move_to(&mut self, x: f32, y: f32) { write!(&mut self.0, "M {} {} ", x, y).unwrap(); } fn line_to(&mut self, x: f32, y: f32) { write!(&mut self.0, "L {} {} ", x, y).unwrap(); } fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap(); } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap(); } fn close(&mut self) { write!(&mut self.0, "Z ").unwrap(); } } // private structs struct Buffer { buffer: Vec, width: usize, height: usize, } impl Buffer { // creates new buffer populated with `val` fn new(val: T, width: usize, height: usize) -> Self { Buffer { buffer: vec![val; width * height], width: width, height: height, } } fn get(&self, col: usize, row: usize) -> T { // TODO return ref mb? clone bad.. // println!("col: {:?}\nrow: {:?}\nget:{:?}", col, row, col + row * self.width); self.buffer.get(col_row_to_index(col, row, self.width)).unwrap().clone() } fn set(&mut self, col: usize, row: usize, val: T) { self.buffer[col_row_to_index(col, row, self.width)] = val; } } #[derive(Clone,Debug,PartialEq)] pub struct Cell { chr: Option, char_attr: CharAttr, } impl Cell { fn new(val: Option) -> Self { Cell { chr: val, char_attr: CharAttr::Normal, } } fn new_with(val: Option, char_attr: CharAttr) -> Self { Cell { chr: val, char_attr: char_attr, } } pub fn chr(&self) -> Option { self.chr } pub fn char_attr(&self) -> CharAttr { self.char_attr.clone() } } struct Cursor { // 1 indexed row number; top to bottom row: usize, // 1 indexed column; left to right col: usize, // current state as for writing characters char_attr: CharAttr, } impl Cursor { fn new() -> Self { Cursor { row: 1, col: 1, char_attr: CharAttr::Normal, } } } // as specified for vt100 #[derive(Clone,Copy,Debug,PartialEq)] pub enum CharAttr { Normal, Bold, Underlined, Blink, Inverse, } // Returns the Vec or Slice index that a 0-indexed column and row correspond to. fn col_row_to_index(col: usize, row: usize, width: usize) -> usize { col + row * width } #[cfg(test)] mod tests { use super::*; mod model { use super::*; #[test] fn set_buffer_at_1_1() { let w = 4; let h = 2; let mut model = Model::new(w, h, 0.005); model.set_buffer_at(1, 1, Cell::new(Some('A'))); let mut blank_buffer = Buffer::new(Cell::new(None), w, h); blank_buffer.buffer[0] = Cell::new(Some('A')); assert_eq!(blank_buffer.buffer, model.buffer.buffer); } #[test] fn set_buffer_at_1_max() { let w = 4; let h = 2; let mut model = Model::new(w, h, 0.005); model.set_buffer_at(1, w, Cell::new(Some('A'))); let mut blank_buffer = Buffer::new(Cell::new(None), w, h); blank_buffer.buffer[w - 1] = Cell::new(Some('A')); assert_eq!(blank_buffer.buffer, model.buffer.buffer); } #[test] fn set_buffer_at_max_max() { let w = 4; let h = 2; let mut model = Model::new(w, h, 0.005); model.set_buffer_at(h, w, Cell::new(Some('A'))); let mut blank_buffer = Buffer::new(Cell::new(None), w, h); blank_buffer.buffer[h * w - 1] = Cell::new(Some('A')); assert_eq!(blank_buffer.buffer, model.buffer.buffer); } #[test] fn set_buffer_at_max_1() { let w = 4; let h = 2; let mut model = Model::new(w, h, 0.005); model.set_buffer_at(h, 1, Cell::new(Some('A'))); let mut blank_buffer = Buffer::new(Cell::new(None), w, h); blank_buffer.buffer[(h - 1) * blank_buffer.width] = Cell::new(Some('A')); assert_eq!(blank_buffer.buffer, model.buffer.buffer); } } #[test] fn r#true() { let result = true; assert!(result, "you can't handle the truth"); } }