From dbda703bcb071c5d3b214987bf6d0e6fff23a8f0 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 10 Jul 2018 11:45:30 +0300 Subject: [PATCH] Add tui as submodule --- .gitmodules | 3 + src/melt_ui/.gitignore | 6 + src/melt_ui/Cargo.toml | 7 + src/melt_ui/src/bin/bin.rs | 40 ++++ src/melt_ui/src/cells.rs | 439 ++++++++++++++++++++++++++++++++++ src/melt_ui/src/components.rs | 267 +++++++++++++++++++++ src/melt_ui/src/lib.rs | 102 ++++++++ src/melt_ui/src/log2 | 1 + src/melt_ui/src/position.rs | 55 +++++ 9 files changed, 920 insertions(+) create mode 100644 .gitmodules create mode 100644 src/melt_ui/.gitignore create mode 100644 src/melt_ui/Cargo.toml create mode 100644 src/melt_ui/src/bin/bin.rs create mode 100644 src/melt_ui/src/cells.rs create mode 100644 src/melt_ui/src/components.rs create mode 100644 src/melt_ui/src/lib.rs create mode 100644 src/melt_ui/src/log2 create mode 100644 src/melt_ui/src/position.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..72d8b9d2a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/melt_ui"] + path = src/melt_ui + url = /home/epilys/Projects/meli/src/melt_ui diff --git a/src/melt_ui/.gitignore b/src/melt_ui/.gitignore new file mode 100644 index 000000000..626bd181d --- /dev/null +++ b/src/melt_ui/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk + +/target +**/*.rs.bk +Cargo.lock diff --git a/src/melt_ui/Cargo.toml b/src/melt_ui/Cargo.toml new file mode 100644 index 000000000..ab7197654 --- /dev/null +++ b/src/melt_ui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "melt_ui" +version = "0.1.0" +authors = ["Manos Pitsidianakis "] + +[dependencies] +termion = "1.5.1" diff --git a/src/melt_ui/src/bin/bin.rs b/src/melt_ui/src/bin/bin.rs new file mode 100644 index 000000000..04b70932e --- /dev/null +++ b/src/melt_ui/src/bin/bin.rs @@ -0,0 +1,40 @@ +extern crate melt_ui; +extern crate termion; + +use melt_ui::{State, Entity, BoxPanel, HSplit, VSplit, TextBox, MailListing}; +use termion::raw::IntoRawMode; + +use std::io::{stdout, stdin, stderr}; +use std::sync::mpsc::{sync_channel, SyncSender, Receiver}; +use std::thread; + +fn main() { + /* Lock all stdios */ + let _stdout = stdout(); + let mut _stdout = _stdout.lock(); + let stdin = stdin(); + let stdin = stdin.lock(); + let _stderr = stderr(); + let mut _stderr = _stderr.lock(); + let mut s = State::new(_stdout.into_raw_mode().unwrap(), stdin); + //s.hello_w(); + + // let ent = Entity { width: 30, height: 30, margin_top: 0, margin_left: 0, component: Box::new(BoxPanel{}) }; + // s.register_entity(ent); + + let a = Entity {component: Box::new(TextBox::new("a text box".to_string())) }; + //let b = Entity { component: Box::new(TextBox::new("b text box".to_string())) }; + let l = Entity { component: Box::new(TextBox::new("left text box".to_string())) }; + let r = Entity { component: Box::new(TextBox::new("right text box".to_string())) }; + let b = Entity { component: Box::new(VSplit::new(l,r,50)) }; + let top = Entity { component: Box::new(HSplit::new(a, b, 70)) }; + //let bottom = Entity { component: Box::new(TextBox::new("hello world.".to_string())) }; + let bottom = Entity { component: Box::new(MailListing::new(200)) }; + let ent = Entity { component: Box::new(HSplit::new(top, bottom, 50)) }; + // + s.register_entity(ent); + s.render(); + + + +} diff --git a/src/melt_ui/src/cells.rs b/src/melt_ui/src/cells.rs new file mode 100644 index 000000000..0f06a3c55 --- /dev/null +++ b/src/melt_ui/src/cells.rs @@ -0,0 +1,439 @@ +use std::ops::{Index, IndexMut, Deref, DerefMut}; +use position::*; + +pub trait CellAccessor: HasSize { + fn cellvec(&self) -> &Vec; + fn cellvec_mut(&mut self) -> &mut Vec; + + /// Clears `self`, using the given `Cell` as a blank. + fn clear(&mut self, blank: Cell) { + for cell in self.cellvec_mut().iter_mut() { + *cell = blank; + } + } + + fn pos_to_index(&self, x: usize, y: usize) -> Option { + let (cols, rows) = self.size(); + if x < cols && y < rows { + Some((cols * y) + x) + } else { + None + } + } + + /// Returns a reference to the `Cell` at the given coordinates, or `None` if the index is out of + /// bounds. + /// + /// # Examples + /// + /// ```no_run + /// use rustty::{Terminal, CellAccessor}; + /// + /// let mut term = Terminal::new().unwrap(); + /// + /// let a_cell = term.get(5, 5); + /// ``` + fn get(&self, x: usize, y: usize) -> Option<&Cell> { + match self.pos_to_index(x, y) { + Some(i) => self.cellvec().get(i), + None => None, + } + } + + /// Returns a mutable reference to the `Cell` at the given coordinates, or `None` if the index + /// is out of bounds. + /// + /// # Examples + /// + /// ```no_run + /// use rustty::{Terminal, CellAccessor}; + /// + /// let mut term = Terminal::new().unwrap(); + /// + /// let a_mut_cell = term.get_mut(5, 5); + /// ``` + fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> { + match self.pos_to_index(x, y) { + Some(i) => self.cellvec_mut().get_mut(i), + None => None, + } + } +} + +/// An array of `Cell`s that represents a terminal display. +/// +/// A `CellBuffer` is a two-dimensional array of `Cell`s, each pair of indices correspond to a +/// single point on the underlying terminal. +/// +/// The first index, `Cellbuffer[y]`, corresponds to a row, and thus the y-axis. The second +/// index, `Cellbuffer[y][x]`, corresponds to a column within a row and thus the x-axis. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CellBuffer { + cols: usize, + rows: usize, + buf: Vec, +} + +impl CellBuffer { + /// Constructs a new `CellBuffer` with the given number of columns and rows, using the given + /// `cell` as a blank. + pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer { + CellBuffer { + cols: cols, + rows: rows, + buf: vec![cell; cols * rows], + } + } + + /// Resizes `CellBuffer` to the given number of rows and columns, using the given `Cell` as + /// a blank. + pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Cell) { + let newlen = newcols * newrows; + let mut newbuf: Vec = Vec::with_capacity(newlen); + for y in 0..newrows { + for x in 0..newcols { + let cell = self.get(x, y).unwrap_or(&blank); + newbuf.push(*cell); + } + } + self.buf = newbuf; + self.cols = newcols; + self.rows = newrows; + } +} + +impl HasSize for CellBuffer { + fn size(&self) -> Size { + (self.cols, self.rows) + } +} + +impl CellAccessor for CellBuffer { + fn cellvec(&self) -> &Vec { + &self.buf + } + + fn cellvec_mut(&mut self) -> &mut Vec { + &mut self.buf + } +} + +impl Deref for CellBuffer { + type Target = [Cell]; + + fn deref<'a>(&'a self) -> &'a [Cell] { + &self.buf + } +} + +impl DerefMut for CellBuffer { + fn deref_mut<'a>(&'a mut self) -> &'a mut [Cell] { + &mut self.buf + } +} + +impl Index for CellBuffer { + type Output = Cell; + + fn index<'a>(&'a self, index: Pos) -> &'a Cell { + let (x, y) = index; + self.get(x, y).expect("index out of bounds") + } +} + +impl IndexMut for CellBuffer { + fn index_mut<'a>(&'a mut self, index: Pos) -> &'a mut Cell { + let (x, y) = index; + self.get_mut(x, y).expect("index out of bounds") + } +} + +impl Default for CellBuffer { + /// Constructs a new `CellBuffer` with a size of `(0, 0)`, using the default `Cell` as a blank. + fn default() -> CellBuffer { + CellBuffer::new(0, 0, Cell::default()) + } +} + +/// A single point on a terminal display. +/// +/// A `Cell` contains a character and style. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Cell { + ch: char, + fg: Color, + bg: Color, + attrs: Attr, +} + +impl Cell { + /// Creates a new `Cell` with the given `char`, `Color`s and `Attr`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let cell = Cell::new('x', Color::Default, Color::Green, Attr::Default); + /// assert_eq!(cell.ch(), 'x'); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Green); + /// assert_eq!(cell.attrs(), Attr::Default); + /// ``` + pub fn new(ch: char, fg: Color, bg: Color, attrs: Attr) -> Cell { + Cell { + ch: ch, + fg: fg, + bg: bg, + attrs: attrs, + } + } + + /// Creates a new `Cell` with the given `char` and default style. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Default); + /// assert_eq!(cell.attrs(), Attr::Default); + /// ``` + pub fn with_char(ch: char) -> Cell { + Cell::new(ch, Color::Default, Color::Default, Attr::Default) + } + + /// Creates a new `Cell` with the given style and a blank `char`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::Bold); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Red); + /// assert_eq!(cell.attrs(), Attr::Bold); + /// assert_eq!(cell.ch(), ' '); + /// ``` + pub fn with_style(fg: Color, bg: Color, attr: Attr) -> Cell { + Cell::new(' ', fg, bg, attr) + } + + /// Returns the `Cell`'s character. + /// + /// # Examples + /// + /// ``` + /// use rustty::Cell; + /// + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// ``` + pub fn ch(&self) -> char { + self.ch + } + + /// Sets the `Cell`'s character to the given `char` + /// + /// # Examples + /// + /// ``` + /// use rustty::Cell; + /// + /// let mut cell = Cell::with_char('x'); + /// assert_eq!(cell.ch(), 'x'); + /// + /// cell.set_ch('y'); + /// assert_eq!(cell.ch(), 'y'); + /// ``` + pub fn set_ch(&mut self, newch: char) -> &mut Cell { + self.ch = newch; + self + } + + /// Returns the `Cell`'s foreground `Color`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::with_style(Color::Blue, Color::Default, Attr::Default); + /// assert_eq!(cell.fg(), Color::Blue); + /// ``` + pub fn get_fg(&self) -> Color { + self.fg + } + + /// Sets the `Cell`'s foreground `Color` to the given `Color`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::default(); + /// assert_eq!(cell.fg(), Color::Default); + /// + /// cell.set_fg(Color::White); + /// assert_eq!(cell.fg(), Color::White); + /// ``` + pub fn set_fg(&mut self, newfg: Color) -> &mut Cell { + self.fg = newfg; + self + } + + /// Returns the `Cell`'s background `Color`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::with_style(Color::Default, Color::Green, Attr::Default); + /// assert_eq!(cell.bg(), Color::Green); + /// ``` + pub fn get_bg(&self) -> Color { + self.bg + } + + /// Sets the `Cell`'s background `Color` to the given `Color`. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color, Attr}; + /// + /// let mut cell = Cell::default(); + /// assert_eq!(cell.bg(), Color::Default); + /// + /// cell.set_bg(Color::Black); + /// assert_eq!(cell.bg(), Color::Black); + /// ``` + pub fn set_bg(&mut self, newbg: Color) -> &mut Cell { + self.bg = newbg; + self + } + + pub fn attrs(&self) -> Attr { + self.attrs + } + + pub fn set_attrs(&mut self, newattrs: Attr) -> &mut Cell { + self.attrs = newattrs; + self + } +} + +impl Default for Cell { + /// Constructs a new `Cell` with a blank `char` and default `Color`s. + /// + /// # Examples + /// + /// ``` + /// use rustty::{Cell, Color}; + /// + /// let mut cell = Cell::default(); + /// assert_eq!(cell.ch(), ' '); + /// assert_eq!(cell.fg(), Color::Default); + /// assert_eq!(cell.bg(), Color::Default); + /// ``` + fn default() -> Cell { + Cell::new(' ', Color::Default, Color::Default, Attr::Default) + } +} + +/// The color of a `Cell`. +/// +/// `Color::Default` represents the default color of the underlying terminal. +/// +/// The eight basic colors may be used directly and correspond to 0x00..0x07 in the 8-bit (256) +/// color range; in addition, the eight basic colors coupled with `Attr::Bold` correspond to +/// 0x08..0x0f in the 8-bit color range. +/// +/// `Color::Byte(..)` may be used to specify a color in the 8-bit range. +/// +/// # Examples +/// +/// ``` +/// use rustty::Color; +/// +/// // The default color. +/// let default = Color::Default; +/// +/// // A basic color. +/// let red = Color::Red; +/// +/// // An 8-bit color. +/// let fancy = Color::Byte(0x01); +/// +/// // Basic colors are also 8-bit colors (but not vice-versa). +/// assert_eq!(red.as_byte(), fancy.as_byte()) +/// ``` +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Color { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + Byte(u8), + Default, +} + +impl Color { + /// Returns the `u8` representation of the `Color`. + pub fn as_byte(&self) -> u8 { + match *self { + Color::Black => 0x00, + Color::Red => 0x01, + Color::Green => 0x02, + Color::Yellow => 0x03, + Color::Blue => 0x04, + Color::Magenta => 0x05, + Color::Cyan => 0x06, + Color::White => 0x07, + Color::Byte(b) => b, + Color::Default => panic!("Attempted to cast default color to u8"), + } + } +} + +/// The attributes of a `Cell`. +/// +/// `Attr` enumerates all combinations of attributes a given style may have. +/// +/// `Attr::Default` represents no attribute. +/// +/// # Examples +/// +/// ``` +/// use rustty::Attr; +/// +/// // Default attribute. +/// let def = Attr::Default; +/// +/// // Base attribute. +/// let base = Attr::Bold; +/// +/// // Combination. +/// let comb = Attr::UnderlineReverse; +/// ``` +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Attr { + Default = 0b000, + Bold = 0b001, + Underline = 0b010, + BoldUnderline = 0b011, + Reverse = 0b100, + BoldReverse = 0b101, + UnderlineReverse = 0b110, + BoldReverseUnderline = 0b111, +} diff --git a/src/melt_ui/src/components.rs b/src/melt_ui/src/components.rs new file mode 100644 index 000000000..8eb692a90 --- /dev/null +++ b/src/melt_ui/src/components.rs @@ -0,0 +1,267 @@ +use std::fmt; +use cells::{Color, Cell, CellBuffer, CellAccessor}; +use position::Pos; + +/// The upper and lower boundary char. +const HORZ_BOUNDARY: char = '─'; +/// The left and right boundary char. +const VERT_BOUNDARY: char = '│'; + +/// The top-left corner +const TOP_LEFT_CORNER: char = '┌'; +/// The top-right corner +const TOP_RIGHT_CORNER: char = '┐'; +/// The bottom-left corner +const BOTTOM_LEFT_CORNER: char = '└'; +/// The bottom-right corner +const BOTTOM_RIGHT_CORNER: char = '┘'; + +const LIGHT_VERTICAL_AND_RIGHT: char = '├'; + +const LIGHT_VERTICAL_AND_LEFT: char = '┤'; + +const LIGHT_DOWN_AND_HORIZONTAL: char = '┬'; + +const LIGHT_UP_AND_HORIZONTAL: char = '┴'; + +pub struct Entity { + //queue: VecDeque, + pub component: Box, // more than one? +} + +impl fmt::Debug for Entity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Entity", ) + } +} + +pub trait Component { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer); + fn process_event(&mut self); +} + + +///A simple box with borders and no content. +pub struct BoxPanel { +} + +impl Component for BoxPanel { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) { + grid[upper_left].set_ch('u'); + grid[bottom_right].set_ch('b'); + let width = bottom_right.0 - upper_left.0; + let height = bottom_right.1 - upper_left.1; + + grid[upper_left].set_ch('┌'); + grid[(upper_left.0, bottom_right.1)].set_ch(BOTTOM_LEFT_CORNER); + grid[(bottom_right.0, upper_left.1)].set_ch('┐'); + grid[bottom_right].set_ch('┘'); + for i in upper_left.1 + 1..bottom_right.1 { + grid[(upper_left.0, i)].set_ch('│'); + grid[(upper_left.0 + width, i)].set_ch('│'); + } + for i in upper_left.0+1..bottom_right.0 { + grid[(i, upper_left.1)].set_ch('─'); + grid[(i, upper_left.1 + height)].set_ch('─'); + } + + let width = bottom_right.0 - upper_left.0; + let height = bottom_right.1 - upper_left.1; + } + fn process_event(&mut self) { + unimplemented!(); + } +} + +/// A horizontally split in half container. +pub struct HSplit { + top: Entity, + bottom: Entity, + ratio: usize, // bottom/whole height * 100 +} + +impl HSplit { + pub fn new(top: Entity, bottom: Entity, ratio: usize) -> Self { + HSplit { + top: top, + bottom: bottom, + ratio: ratio, + } + } +} + + +impl Component for HSplit { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) { + //eprintln!("grid {:?}", grid); + grid[upper_left].set_ch('u'); + let (a,b) = upper_left; + grid[(a+1,b)].set_ch('h'); + let width = bottom_right.0 - upper_left.0; + let height = bottom_right.1 - upper_left.1; + + let total_rows = bottom_right.1 - upper_left.1; + let bottom_entity_height = (self.ratio*total_rows )/100; + let mid = upper_left.1 + total_rows - bottom_entity_height; + + for i in upper_left.0..bottom_right.0+1 { + grid[(i, mid)].set_ch('─'); + } + let _ = self.top.component.draw(upper_left, (bottom_right.0, upper_left.1 + mid-1), grid); + let _ = self.bottom.component.draw((upper_left.0, upper_left.1 + mid), bottom_right, grid); + grid[bottom_right].set_ch('b'); + } + fn process_event(&mut self) { + unimplemented!(); + } +} + +/// A horizontally split in half container. +pub struct VSplit { + left: Entity, + right: Entity, + ratio: usize, // right/(container width) * 100 +} + +impl VSplit { + pub fn new(left: Entity, right: Entity, ratio: usize) -> Self { + VSplit { + left: left, + right: right, + ratio: ratio, + } + } +} + + +impl Component for VSplit { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) { + + eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right); + let width = bottom_right.0 - upper_left.0; + let height = bottom_right.1 - upper_left.1; + + let total_cols = bottom_right.0 - upper_left.0; + let right_entity_width = (self.ratio*total_cols )/100; + let mid = bottom_right.0 - right_entity_width; + eprintln!("total_cols {:?}, right_entity_width: {:?}, mid: {:?}",total_cols, right_entity_width, mid); + if (upper_left.1> 1) { + let c = grid.get(mid, upper_left.1-1).map(|a| a.ch()).unwrap_or_else(|| ' '); + match c { + HORZ_BOUNDARY => { + grid[(mid, upper_left.1-1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL); + }, + _ => {}, + } + } + + for i in upper_left.1..bottom_right.1 { + grid[(mid, i)].set_ch(VERT_BOUNDARY); + } + if (bottom_right.1> 1) { + let c = grid.get(mid, bottom_right.1-1).map(|a| a.ch()).unwrap_or_else(|| ' '); + match c { + HORZ_BOUNDARY => { + grid[(mid, bottom_right.1+1)].set_ch(LIGHT_UP_AND_HORIZONTAL); + }, + _ => {}, + } + } + let _ = self.left.component.draw(upper_left, (mid-1, bottom_right.1), grid); + let _ = self.right.component.draw((mid+1, upper_left.1), bottom_right, grid); + } + fn process_event(&mut self) { + unimplemented!(); + } +} + +/// A box with a text content. +pub struct TextBox { + content: String, +} + +impl TextBox { + pub fn new(s: String) -> Self { + TextBox { + content: s, + } + } +} + +impl Component for TextBox { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) { + let mut x = upper_left.0; + let mut y = upper_left.1; + for c in self.content.chars() { + grid[(x,y)].set_ch(c); + //eprintln!("printed {} in ({}, {})", c, x, y); + x += 1; + if x == bottom_right.0 + 1 { + x = upper_left.0; + } + + if y == bottom_right.1 { + break; + } + } + //eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right); + let width = bottom_right.0 - upper_left.0; + let height = bottom_right.1 - upper_left.1; + } + fn process_event(&mut self) { + unimplemented!(); + } +} + +const max_width: usize = 500; + +pub struct MailListing { + cursor_pos: usize, + length: usize, + // sorting + content: CellBuffer, + unfocused: bool, + // content (2-d vec of bytes) or Cells? + // current view on top of content + // active or not? for key events + +} + +impl MailListing { + pub fn new(length: usize) -> Self { + MailListing { + cursor_pos: 0, + length: length, + content: CellBuffer::new(max_width, length+1, Cell::with_char(' ')), + unfocused: false, + } + } +} + +impl Component for MailListing { + fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) { + let mut height = 0; + let mut stripe = false; + for y in upper_left.1..bottom_right.1 { + if height == self.length { + /* for loop */ + for _y in y..bottom_right.1 { + for x in upper_left.0..bottom_right.0 { + grid[(x,y)].set_ch(' '); + } + } + break; + } + for x in upper_left.0..bottom_right.0 { + //grid[(x,y)].set_ch(self.content[(x-upper_left.0+1, y-upper_left.1+1)].ch()); + grid[(x,y)].set_ch(if stripe { 't' } else { 'f'} ); + grid[(x,y)].set_bg(if stripe { Color::Byte(246) } else {Color::Default }); + } + stripe = if stripe { false } else { true }; + height +1 ; + } + } + fn process_event(&mut self) { + unimplemented!(); + } +} diff --git a/src/melt_ui/src/lib.rs b/src/melt_ui/src/lib.rs new file mode 100644 index 000000000..2ca049bf9 --- /dev/null +++ b/src/melt_ui/src/lib.rs @@ -0,0 +1,102 @@ +extern crate termion; + + +use termion::{clear, cursor}; + + + +//use std::env; +use std::io::{Read, Write}; +//use std::collections::VecDeque; +//use std::process; + + +mod components; +mod position; +mod cells; +use cells::{Cell, CellBuffer}; +use position::Pos; + +pub use self::components::*; +pub use self::position::*; + + +pub struct UIEvent { +} + +pub struct State { + width: usize, + height: usize, + + grid: CellBuffer, + stdin: R, + pub stdout: W, + entities: Vec, + +} + +impl State { + pub fn new(stdout: W, stdin: R) -> Self { + let termsize = termion::terminal_size().ok(); + let termwidth = termsize.map(|(w,_)| w); + let termheight = termsize.map(|(_,h)| h); + let width = termwidth.unwrap_or(0) as usize; + let height = termheight.unwrap_or(0) as usize; + let mut s = State { + width: width, + height: height, + //queue: VecDeque::new(); + + grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')), + stdin: stdin, + stdout: stdout, + entities: Vec::with_capacity(2), + }; + write!(s.stdout, "{}{}", clear::All, cursor::Goto(1,1)).unwrap(); + s + } + pub fn hello_w(&mut self) { + write!(self.stdout, "Hey there.").unwrap(); + } + fn update_size(&mut self) { + /* update dimensions. TODO: Only do that in size change events. ie SIGWINCH */ + let termsize = termion::terminal_size().ok(); + let termwidth = termsize.map(|(w,_)| w); + let termheight = termsize.map(|(_,h)| h); + self.width = termwidth.unwrap_or(72) as usize; + self.height = termheight.unwrap_or(120) as usize; + } + + pub fn render(&mut self) { + self.update_size(); + + /* draw each entity */ for i in 0..self.entities.len() { + self.draw_entity(i); + } + + for y in 1..self.height { + write!(self.stdout, "{}", cursor::Goto(1,y as u16)).unwrap(); + for x in 1..self.width { + let c = self.grid[(x,y)]; + if c.get_bg() == cells::Color::Default { + write!(self.stdout, "{}",c.ch()).unwrap(); + } else { + write!(self.stdout, "{}{}{}", termion::color::Bg(termion::color::LightBlack),c.ch(),termion::color::Bg(termion::color::Reset)).unwrap(); + } + + } + } + } + pub fn draw_entity(&mut self, idx: usize) { + let ref mut entity = self.entities[idx]; + eprintln!("Entity is {:?}", entity); + let upper_left = (1,1); + let bottom_right = (self.width, self.height); + eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right); + + entity.component.draw(upper_left, bottom_right, &mut self.grid); + } + pub fn register_entity(&mut self, entity: Entity) { + self.entities.push(entity); + } +} diff --git a/src/melt_ui/src/log2 b/src/melt_ui/src/log2 new file mode 100644 index 000000000..ef9105275 --- /dev/null +++ b/src/melt_ui/src/log2 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/melt_ui/src/position.rs b/src/melt_ui/src/position.rs new file mode 100644 index 000000000..03d2fdfaf --- /dev/null +++ b/src/melt_ui/src/position.rs @@ -0,0 +1,55 @@ +/// A `(x, y)` position on screen. +pub type Pos = (usize, usize); +/// A `(cols, rows)` size. +pub type Size = (usize, usize); + +pub trait HasSize { + fn size(&self) -> Size; +} + +pub trait HasPosition { + fn origin(&self) -> Pos; + fn set_origin(&mut self, new_origin: Pos); +} + +/// A cursor position. +pub struct Cursor { + pos: Option, + last_pos: Option, +} + +impl Cursor { + pub fn new() -> Cursor { + Cursor { + pos: None, + last_pos: None, + } + } + + /// Checks whether the current and last coordinates are sequential and returns `true` if they + /// are and `false` otherwise. + pub fn is_seq(&self) -> bool { + if let Some((cx, cy)) = self.pos { + if let Some((lx, ly)) = self.last_pos { + (lx + 1, ly) == (cx, cy) + } else { + false + } + } else { + false + } + } + + pub fn pos(&self) -> Option { + self.pos + } + + pub fn set_pos(&mut self, newpos: Option) { + self.last_pos = self.pos; + self.pos = newpos; + } + + pub fn invalidate_last_pos(&mut self) { + self.last_pos = None; + } +}