2016-03-31 16:19:08 +00:00
|
|
|
|
2017-03-10 13:50:37 +00:00
|
|
|
#[derive(Clone, PartialEq)]
|
2016-03-31 16:19:08 +00:00
|
|
|
pub struct Color(pub f64, pub f64, pub f64);
|
|
|
|
|
2016-04-05 22:04:40 +00:00
|
|
|
pub const COLOR_BLACK: Color = Color(0.0, 0.0, 0.0);
|
|
|
|
pub const COLOR_WHITE: Color = Color(1.0, 1.0, 1.0);
|
2017-03-09 10:19:44 +00:00
|
|
|
pub const COLOR_RED: Color = Color(1.0, 0.0, 0.0);
|
2016-03-31 16:19:08 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Attrs {
|
2016-04-01 09:34:55 +00:00
|
|
|
pub italic: bool,
|
|
|
|
pub bold: bool,
|
2017-03-09 10:19:44 +00:00
|
|
|
pub underline: bool,
|
|
|
|
pub undercurl: bool,
|
2016-04-05 22:04:40 +00:00
|
|
|
pub foreground: Option<Color>,
|
|
|
|
pub background: Option<Color>,
|
2017-03-09 10:19:44 +00:00
|
|
|
pub special: Option<Color>,
|
2017-03-10 09:32:58 +00:00
|
|
|
pub reverse: bool,
|
2017-03-12 09:20:18 +00:00
|
|
|
pub double_width: bool,
|
2016-03-31 16:19:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Attrs {
|
|
|
|
pub fn new() -> Attrs {
|
|
|
|
Attrs {
|
2016-04-05 22:04:40 +00:00
|
|
|
foreground: None,
|
|
|
|
background: None,
|
2017-03-09 10:19:44 +00:00
|
|
|
special: None,
|
2016-04-01 09:34:55 +00:00
|
|
|
italic: false,
|
|
|
|
bold: false,
|
2017-03-09 10:19:44 +00:00
|
|
|
underline: false,
|
|
|
|
undercurl: false,
|
2017-03-10 09:32:58 +00:00
|
|
|
reverse: false,
|
2017-03-12 09:20:18 +00:00
|
|
|
double_width: false,
|
2016-03-31 16:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-03 13:04:52 +00:00
|
|
|
|
|
|
|
fn clear(&mut self) {
|
|
|
|
self.italic = false;
|
|
|
|
self.bold = false;
|
2017-03-09 10:19:44 +00:00
|
|
|
self.underline = false;
|
|
|
|
self.undercurl = false;
|
2017-03-10 09:32:58 +00:00
|
|
|
self.reverse = false;
|
2016-04-05 22:04:40 +00:00
|
|
|
self.foreground = None;
|
|
|
|
self.background = None;
|
2017-03-09 10:19:44 +00:00
|
|
|
self.special = None;
|
2017-03-12 09:20:18 +00:00
|
|
|
self.double_width = false;
|
2016-04-03 13:04:52 +00:00
|
|
|
}
|
2016-03-31 16:19:08 +00:00
|
|
|
}
|
|
|
|
|
2016-04-03 15:13:18 +00:00
|
|
|
#[derive(Clone)]
|
2016-03-19 08:47:23 +00:00
|
|
|
pub struct Cell {
|
2016-03-31 16:19:08 +00:00
|
|
|
pub ch: char,
|
|
|
|
pub attrs: Attrs,
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Cell {
|
|
|
|
pub fn new(ch: char) -> Cell {
|
2016-03-31 16:19:08 +00:00
|
|
|
Cell {
|
|
|
|
ch: ch,
|
|
|
|
attrs: Attrs::new(),
|
|
|
|
}
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
2016-03-29 09:22:16 +00:00
|
|
|
|
|
|
|
fn clear(&mut self) {
|
2016-03-31 16:19:08 +00:00
|
|
|
self.ch = ' ';
|
2016-04-03 13:04:52 +00:00
|
|
|
self.attrs.clear();
|
2016-03-29 09:22:16 +00:00
|
|
|
}
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct UiModel {
|
2016-04-03 22:34:44 +00:00
|
|
|
pub columns: usize,
|
|
|
|
pub rows: usize,
|
2016-03-28 15:05:10 +00:00
|
|
|
cur_row: usize,
|
|
|
|
cur_col: usize,
|
|
|
|
model: Vec<Vec<Cell>>,
|
2016-04-03 15:13:18 +00:00
|
|
|
top: usize,
|
|
|
|
bot: usize,
|
|
|
|
left: usize,
|
|
|
|
right: usize,
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UiModel {
|
2016-03-19 10:27:39 +00:00
|
|
|
pub fn empty() -> UiModel {
|
2017-03-09 13:51:34 +00:00
|
|
|
UiModel {
|
|
|
|
columns: 0,
|
|
|
|
rows: 0,
|
|
|
|
cur_row: 0,
|
|
|
|
cur_col: 0,
|
|
|
|
model: vec![],
|
|
|
|
top: 0,
|
|
|
|
bot: 0,
|
|
|
|
left: 0,
|
|
|
|
right: 0,
|
|
|
|
}
|
2016-03-19 10:27:39 +00:00
|
|
|
}
|
|
|
|
|
2016-03-28 15:05:10 +00:00
|
|
|
pub fn new(rows: u64, columns: u64) -> UiModel {
|
|
|
|
let mut model = Vec::with_capacity(rows as usize);
|
|
|
|
for i in 0..rows as usize {
|
|
|
|
model.push(Vec::with_capacity(columns as usize));
|
2016-03-31 16:19:08 +00:00
|
|
|
for _ in 0..columns as usize {
|
2016-03-28 15:05:10 +00:00
|
|
|
model[i].push(Cell::new(' '));
|
|
|
|
}
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
|
|
|
|
2016-03-31 16:19:08 +00:00
|
|
|
UiModel {
|
2016-03-28 15:05:10 +00:00
|
|
|
columns: columns as usize,
|
|
|
|
rows: rows as usize,
|
2016-03-19 08:47:23 +00:00
|
|
|
cur_row: 0,
|
|
|
|
cur_col: 0,
|
|
|
|
model: model,
|
2016-04-03 15:13:18 +00:00
|
|
|
top: 0,
|
2017-03-09 13:51:34 +00:00
|
|
|
bot: (rows - 1) as usize,
|
2016-04-03 15:13:18 +00:00
|
|
|
left: 0,
|
2017-03-09 13:51:34 +00:00
|
|
|
right: (columns - 1) as usize,
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-31 16:19:08 +00:00
|
|
|
pub fn model(&self) -> &Vec<Vec<Cell>> {
|
|
|
|
&self.model
|
2016-03-31 10:09:34 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
pub fn cur_point(&self) -> ModelRect {
|
|
|
|
ModelRect::point(self.cur_row, self.cur_col)
|
|
|
|
}
|
|
|
|
|
2017-03-17 20:38:12 +00:00
|
|
|
pub fn set_cursor(&mut self, row: u64, col: u64) -> ModelRect {
|
2017-03-18 09:45:05 +00:00
|
|
|
let mut changed_region = self.cur_point();
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2016-03-28 15:05:10 +00:00
|
|
|
self.cur_row = row as usize;
|
2016-05-03 17:25:52 +00:00
|
|
|
self.cur_col = col as usize;
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
changed_region.join(&self.cur_point());
|
2017-03-17 20:38:12 +00:00
|
|
|
|
|
|
|
changed_region
|
|
|
|
|
2016-05-03 17:25:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_cursor(&self) -> (usize, usize) {
|
|
|
|
(self.cur_row, self.cur_col)
|
2016-03-28 15:05:10 +00:00
|
|
|
}
|
|
|
|
|
2017-03-17 20:38:12 +00:00
|
|
|
pub fn put(&mut self, text: &str, attrs: Option<&Attrs>) -> ModelRect {
|
2017-03-18 09:45:05 +00:00
|
|
|
let changed_region = self.cur_point();
|
2016-03-31 16:19:08 +00:00
|
|
|
let mut cell = &mut self.model[self.cur_row][self.cur_col];
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2017-03-12 09:20:18 +00:00
|
|
|
cell.ch = text.chars().last().unwrap_or(' ');
|
2017-03-09 20:45:45 +00:00
|
|
|
cell.attrs = attrs.map(Attrs::clone).unwrap_or_else(|| Attrs::new());
|
2017-03-12 09:20:18 +00:00
|
|
|
cell.attrs.double_width = text.len() == 0;
|
2016-03-28 15:05:10 +00:00
|
|
|
self.cur_col += 1;
|
2017-03-17 20:38:12 +00:00
|
|
|
|
|
|
|
changed_region
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
2016-03-29 09:22:16 +00:00
|
|
|
|
2016-04-03 15:13:18 +00:00
|
|
|
pub fn set_scroll_region(&mut self, top: u64, bot: u64, left: u64, right: u64) {
|
|
|
|
self.top = top as usize;
|
|
|
|
self.bot = bot as usize;
|
|
|
|
self.left = left as usize;
|
|
|
|
self.right = right as usize;
|
|
|
|
}
|
|
|
|
|
2017-03-17 20:38:12 +00:00
|
|
|
#[inline]
|
|
|
|
fn copy_row(&mut self, row: usize, offset: usize, left: usize, right: usize) {
|
2016-04-03 15:13:18 +00:00
|
|
|
for col in left..right + 1 {
|
2017-03-17 20:38:12 +00:00
|
|
|
let from_row = row + offset;
|
2017-02-26 19:33:44 +00:00
|
|
|
let from_cell = self.model[from_row][col].clone();
|
|
|
|
self.model[row][col] = from_cell;
|
|
|
|
}
|
2016-04-03 15:13:18 +00:00
|
|
|
}
|
|
|
|
|
2017-03-17 20:38:12 +00:00
|
|
|
pub fn scroll(&mut self, count: usize) -> ModelRect {
|
|
|
|
let (top, bot, left, right) = (self.top, self.bot, self.left, self.right);
|
2016-04-03 15:13:18 +00:00
|
|
|
|
|
|
|
if count > 0 {
|
2017-03-17 20:38:12 +00:00
|
|
|
for row in top..(bot - count + 1) {
|
2016-04-03 15:13:18 +00:00
|
|
|
self.copy_row(row, count, left, right);
|
|
|
|
}
|
|
|
|
} else {
|
2017-03-17 20:38:12 +00:00
|
|
|
for row in ((top - count)..(bot + 1)).rev() {
|
2016-04-03 15:13:18 +00:00
|
|
|
self.copy_row(row, count, left, right);
|
2016-03-29 09:22:16 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-03 15:13:18 +00:00
|
|
|
|
|
|
|
if count > 0 {
|
2017-03-17 20:38:12 +00:00
|
|
|
self.clear_region((bot - count + 1), bot, left, right);
|
2016-04-03 15:13:18 +00:00
|
|
|
} else {
|
2017-03-17 20:38:12 +00:00
|
|
|
self.clear_region(top, (top - count - 1), left, right);
|
2016-04-03 15:13:18 +00:00
|
|
|
}
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
ModelRect::new(top, bot, left, right)
|
2016-04-03 15:13:18 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
pub fn clear(&mut self) {
|
2016-04-03 15:13:18 +00:00
|
|
|
let (rows, columns) = (self.rows, self.columns);
|
|
|
|
self.clear_region(0, rows - 1, 0, columns - 1);
|
2016-03-29 09:22:16 +00:00
|
|
|
}
|
2016-04-03 13:04:52 +00:00
|
|
|
|
2017-03-17 20:38:12 +00:00
|
|
|
pub fn eol_clear(&mut self) -> ModelRect {
|
2016-04-03 13:04:52 +00:00
|
|
|
let (cur_row, cur_col, columns) = (self.cur_row, self.cur_col, self.columns);
|
|
|
|
self.clear_region(cur_row, cur_row, cur_col, columns - 1);
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
ModelRect::new(cur_row, cur_col, cur_col, columns - 1)
|
2016-04-03 13:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_region(&mut self, top: usize, bot: usize, left: usize, right: usize) {
|
2016-04-03 15:13:18 +00:00
|
|
|
for row in &mut self.model[top..bot + 1] {
|
|
|
|
for cell in &mut row[left..right + 1] {
|
|
|
|
cell.clear();
|
2016-04-03 13:04:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 08:47:23 +00:00
|
|
|
}
|
2017-03-17 20:38:12 +00:00
|
|
|
|
2017-03-18 09:45:05 +00:00
|
|
|
#[derive(Clone)]
|
2017-03-17 20:38:12 +00:00
|
|
|
pub struct ModelRect {
|
|
|
|
top: usize,
|
|
|
|
bot: usize,
|
|
|
|
left: usize,
|
|
|
|
right: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ModelRect {
|
|
|
|
pub fn new(top: usize, bot: usize, left: usize, right: usize) -> ModelRect {
|
|
|
|
ModelRect {
|
|
|
|
top: top,
|
|
|
|
bot: bot,
|
|
|
|
left: left,
|
|
|
|
right: right,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn point(x: usize, y: usize) -> ModelRect {
|
|
|
|
ModelRect {
|
|
|
|
top: x,
|
|
|
|
bot: x,
|
|
|
|
left: y,
|
|
|
|
right: y,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn join(&mut self, rect: &ModelRect) {
|
|
|
|
self.top = if self.top < rect.top {
|
|
|
|
self.top
|
|
|
|
} else {
|
|
|
|
rect.top
|
|
|
|
};
|
|
|
|
self.left = if self.left < rect.left {
|
|
|
|
self.left
|
|
|
|
} else {
|
|
|
|
rect.left
|
|
|
|
};
|
|
|
|
|
|
|
|
self.bot = if self.bot > rect.bot {
|
|
|
|
self.bot
|
|
|
|
} else {
|
|
|
|
rect.bot
|
|
|
|
};
|
|
|
|
self.right = if self.right > rect.right {
|
|
|
|
self.right
|
|
|
|
} else {
|
|
|
|
rect.right
|
|
|
|
};
|
|
|
|
}
|
2017-03-17 22:18:41 +00:00
|
|
|
|
|
|
|
pub fn to_area(&self, line_height: i32, char_width: i32) -> (i32, i32, i32, i32) {
|
2017-03-18 09:45:05 +00:00
|
|
|
(self.left as i32 * char_width,
|
|
|
|
self.top as i32 * line_height,
|
|
|
|
(self.right - self.left + 1) as i32 * char_width,
|
|
|
|
(self.bot - self.top + 1) as i32 * line_height)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_cursor_area() {
|
|
|
|
let mut model = UiModel::new(10, 20);
|
|
|
|
|
|
|
|
model.set_cursor(1, 1);
|
|
|
|
|
|
|
|
let rect = model.set_cursor(5, 5);
|
|
|
|
|
|
|
|
assert_eq!(1, rect.top);
|
|
|
|
assert_eq!(1, rect.left);
|
|
|
|
assert_eq!(5, rect.bot);
|
|
|
|
assert_eq!(5, rect.right);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_eol_clear_area() {
|
|
|
|
let mut model = UiModel::new(10, 20);
|
|
|
|
|
|
|
|
model.set_cursor(1, 1);
|
|
|
|
|
|
|
|
let rect = model.eol_clear();
|
|
|
|
|
|
|
|
assert_eq!(1, rect.top);
|
|
|
|
assert_eq!(1, rect.left);
|
|
|
|
assert_eq!(1, rect.bot);
|
|
|
|
assert_eq!(19, rect.right);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_repaint_rect() {
|
|
|
|
let rect = ModelRect::point(1, 1);
|
|
|
|
let (x, y, width, height) = rect.to_area(10, 5);
|
|
|
|
|
|
|
|
assert_eq!(5, x);
|
|
|
|
assert_eq!(10, y);
|
|
|
|
assert_eq!(5, width);
|
|
|
|
assert_eq!(10, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_put_area() {
|
|
|
|
let mut model = UiModel::new(10, 20);
|
|
|
|
|
|
|
|
model.set_cursor(1, 1);
|
|
|
|
|
|
|
|
let rect = model.put(" ", None);
|
|
|
|
|
|
|
|
assert_eq!(1, rect.top);
|
|
|
|
assert_eq!(1, rect.left);
|
|
|
|
assert_eq!(1, rect.bot);
|
|
|
|
assert_eq!(1, rect.right);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scroll_area() {
|
|
|
|
let mut model = UiModel::new(10, 20);
|
|
|
|
|
|
|
|
model.set_scroll_region(1, 5, 1, 5);
|
|
|
|
|
|
|
|
let rect = model.scroll(3);
|
|
|
|
|
|
|
|
assert_eq!(1, rect.top);
|
|
|
|
assert_eq!(1, rect.left);
|
|
|
|
assert_eq!(5, rect.bot);
|
|
|
|
assert_eq!(5, rect.right);
|
2017-03-17 22:18:41 +00:00
|
|
|
}
|
2017-03-17 20:38:12 +00:00
|
|
|
}
|