diff options
Diffstat (limited to 'src/structs.rs')
| -rw-r--r-- | src/structs.rs | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..354fe70 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,495 @@ +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<u32>, + buffer: Buffer<Cell>, + 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<u32> { &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<T: std::clone::Clone> { + buffer: Vec<T>, + width: usize, + height: usize, +} +impl<T: Clone> Buffer<T> { + // 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>, + char_attr: CharAttr, +} +impl Cell { + fn new(val: Option<char>) -> Self { + Cell { + chr: val, + char_attr: CharAttr::Normal, + } + } + fn new_with(val: Option<char>, char_attr: CharAttr) -> Self { + Cell { + chr: val, + char_attr: char_attr, + } + } + pub fn chr(&self) -> Option<char> { + 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"); + } +} |
