496 lines
16 KiB
Rust
496 lines
16 KiB
Rust
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");
|
|
}
|
|
}
|