commit 67c973c0af1d35f0f832d4eb594c12868ef78008 Author: rabite Date: Mon Jan 21 14:44:34 2019 +0100 First commit, did some refactoring around widgets, etc, etc diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..40d72ff --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hunter" +version = "0.1.0" +authors = ["project"] +edition = "2018" + +[dependencies] +termion = "*" +unicode-width = "0.1.5" +lazy_static = "*" +x11-clipboard = "*" \ No newline at end of file diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..a35a4b1 --- /dev/null +++ b/src/files.rs @@ -0,0 +1,62 @@ +use std::ops::Index; + +pub struct Files(Vec); + +impl Index for Files { + type Output = File; + fn index(&self, pos: usize) -> &Self::Output { + &self.0[pos] + } +} + +impl Files { + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +#[derive(Debug)] +pub struct File { + pub name: String, + pub path: String, + pub size: Option, + // owner: Option, + // group: Option, + // flags: Option, + // ctime: Option, + // mtime: Option, +} + + +impl File { + pub fn new(name: &str, path: &str, size: usize) -> File { + File { + name: name.to_string(), + path: path.to_string(), + size: Some(size), + // owner: None, + // group: None, + // flags: None, + // ctime: None, + // mtime: None + } + } +} + +pub fn get_files(dir: &str) -> Result { + let mut files = Vec::new(); + for file in std::fs::read_dir(dir)? { + let name = file.as_ref().unwrap().file_name().into_string().unwrap(); + file.as_ref().unwrap().path().pop(); + let path = file.as_ref().unwrap().path().into_os_string().into_string().unwrap(); + let size = file.unwrap().metadata()?.len() / 1024; + files.push(File::new(&name, &path, size as usize)); + } + Ok(Files(files)) +} + + diff --git a/src/hbox.rs b/src/hbox.rs new file mode 100644 index 0000000..3be950a --- /dev/null +++ b/src/hbox.rs @@ -0,0 +1,51 @@ +use crate::widget::Widget; + +pub struct HBox { + dimensions: (u16, u16), + position: (u16, u16), + children: Vec>, + main: usize +} + +impl HBox { + pub fn new(widgets: Vec>) -> HBox { + HBox { + dimensions: (100, 100), + position: (1, 1), + children: widgets, + main: 0 + } + } +} + +impl Widget for HBox { + fn render(&self) -> Vec { + // self.children.iter().map(|child| { + // child.render() + // }).collect() + vec![] + } + + fn render_header(&self) -> String { + self.children[self.main].render_header() + } + + fn refresh(&mut self) { + for child in &mut self.children { + child.refresh(); + } + } + + fn get_drawlist(&mut self) -> String { + self.children.iter_mut().map(|child| { + child.get_drawlist() + }).collect() + } + + fn get_dimensions(&self) -> (u16, u16) { + self.dimensions + } + fn get_position(&self) -> (u16, u16) { + self.position + } +} diff --git a/src/listview.rs b/src/listview.rs new file mode 100644 index 0000000..8fab137 --- /dev/null +++ b/src/listview.rs @@ -0,0 +1,125 @@ +use termion::event::{Key}; + +use crate::term; +use crate::files::Files; +use crate::widget::Widget; +use crate::window::{STATUS_BAR_MARGIN, HEADER_MARGIN}; + +pub struct ListView { + pub content: T, + selection: usize, + offset: usize, + buffer: Vec, + dimensions: (u16, u16), + position: (u16, u16), +} + +impl ListView where ListView: Widget { + pub fn new(content: T, dimensions: (u16, u16), position: (u16, u16)) -> Self { + let view = ListView:: { + content: content, + selection: 0, + offset: 0, + buffer: Vec::new(), + dimensions: dimensions, + position: position + }; + view + } + pub fn to_trait(self) -> Box { + Box::new(self) + } + + fn move_up(&mut self) { + if self.selection == 0 { + return; + } + + if self.selection - self.offset <= 0 { + self.offset -= 1; + } + + self.selection -= 1; + } + fn move_down(&mut self) { + let len = self.buffer.len(); + let y_size = self.dimensions.1 as usize; + + if self.selection == len - 1 { + return; + } + + if self.selection >= y_size - HEADER_MARGIN - STATUS_BAR_MARGIN + && self.selection - self.offset >= y_size - HEADER_MARGIN - STATUS_BAR_MARGIN + { + self.offset += 1; + } + + self.selection += 1; + } + +} + +impl Widget for ListView { + fn get_dimensions(&self) -> (u16, u16) { + self.dimensions + } + fn get_position(&self) -> (u16, u16) { + self.position + } + fn refresh(&mut self) { + self.buffer = self.render(); + } + + fn render(&self) -> Vec { + self.content.iter().map(|file| { + self.render_line(&file.name, + &format!("{:?}", file.size), + false) + }).collect() + } + + fn get_drawlist(&mut self) -> String { + let mut output = term::reset(); + let (_xsize, ysize) = self.dimensions; + let (xpos, ypos) = self.position; + output += &term::reset(); + + + for (i, item) in self.buffer + .iter() + .skip(self.offset) + .take(ysize as usize) + .enumerate() + { + output += &term::normal_color(); + + if i == (self.selection - self.offset) { + output += &term::invert(); + } + output += &format!("{}{}{}", term::goto_xy(xpos, i as u16 + ypos - 1 ), item, term::reset()); + } + + + // if ysize as usize > self.buffer.len() { + // let start_y = self.buffer.len() + 1; + // for i in start_y..ysize as usize { + // output += &format!("{}{:xsize$}{}", term::gotoy(i), " ", xsize = xsize as usize); + // } + // } + + output + } + fn render_header(&self) -> String { + format!("{} files", self.content.len()) + } + + fn on_key(&mut self, key: Key) { + match key { + Key::Up => { self.move_up(); self.refresh() }, + Key::Down => { self.move_down(); self.refresh() }, + //Key::Right => self.go(), + _ => {} + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c0e786f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,46 @@ +extern crate termion; +extern crate unicode_width; +#[macro_use] +extern crate lazy_static; + +use std::io::{stdout, Write}; + + +use termion::screen::AlternateScreen; + +use termion::input::MouseTerminal; +use termion::raw::IntoRawMode; + +mod term; +mod window; +mod listview; +mod files; +mod win_main; +mod widget; +mod hbox; + +use listview::ListView; +use window::Window; +use hbox::HBox; + + +fn main() { + // Need to do this here to actually turn terminal into raw mode... + let mut _screen = AlternateScreen::from(Box::new(stdout())); + let mut _stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap()); + + let files = files::get_files("/home/project/").unwrap(); + let listview = ListView::new(files, (50,20), (10,10)); + + let files = files::get_files("/home/project/").unwrap(); + let listview2 = ListView::new(files, (50,20), (80,10)); + + let boxed = vec![listview.to_trait(), listview2.to_trait()]; + + let hbox = HBox::new(boxed); + + let mut win = Window::new(hbox); + win.run(); + + write!(_stdout, "{}", termion::cursor::Show).unwrap(); +} diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 0000000..c559ad8 --- /dev/null +++ b/src/term.rs @@ -0,0 +1,84 @@ +use std::io::{Stdout, Write}; +use termion; +use termion::screen::AlternateScreen; + +pub trait ScreenExt: Write { + fn cursor_hide(&mut self) { + write!(self, "{}", termion::cursor::Hide).unwrap(); + } + fn cursor_show(&mut self) { + write!(self, "{}", termion::cursor::Show).unwrap(); + } + fn reset(&mut self) { + write!(self, "{}", termion::style::Reset).unwrap(); + } +} + +impl ScreenExt for AlternateScreen> {} + +pub fn xsize() -> usize { + let (xsize, _) = termion::terminal_size().unwrap(); + xsize as usize +} + +pub fn ysize() -> usize { + let (_, ysize) = termion::terminal_size().unwrap(); + ysize as usize +} + +pub fn highlight_color() -> String { + format!( + "{}{}", + termion::color::Fg(termion::color::LightGreen), + termion::color::Bg(termion::color::Black) + ) +} + +pub fn normal_color() -> String { + format!( + "{}{}", + termion::color::Fg(termion::color::LightBlue), + termion::color::Bg(termion::color::Black) + ) +} + +pub fn cursor_left(n: usize) -> String { + format!("{}", termion::cursor::Left(n as u16)) +} + +pub fn gotoy(y: usize) -> String { + format!("{}", termion::cursor::Goto(1, y as u16)) +} + +pub fn goto_xy(x: u16, y: u16) -> String { + format!("{}", termion::cursor::Goto(x, y)) +} + +// pub fn move_top() -> String { +// gotoy(1) +// } + +pub fn move_bottom() -> String { + gotoy(ysize()) +} + +pub fn reset() -> String { + format!("{}", termion::style::Reset) +} + +pub fn invert() -> String { + format!("{}", termion::style::Invert) +} + +pub fn header_color() -> String { + format!( + "{}{}", + termion::color::Fg(termion::color::White), + termion::color::Bg(termion::color::Blue) + ) +} + +pub fn status_bg() -> String { + format!("{}", termion::color::Bg(termion::color::LightBlue)) +} + diff --git a/src/widget.rs b/src/widget.rs new file mode 100644 index 0000000..8b9c166 --- /dev/null +++ b/src/widget.rs @@ -0,0 +1,91 @@ +use termion::event::{Key, MouseEvent, Event}; +use unicode_width::{UnicodeWidthStr}; + +use crate::term; + + +pub trait Widget { + fn render(&self) -> Vec; + fn get_dimensions(&self) -> (u16, u16); + fn get_position(&self) -> (u16, u16); + fn render_line(&self, left: &str, right: &str, highlight: bool) -> String { + let (xsize, _) = self.get_dimensions(); + let text_color = match highlight { + true => term::highlight_color(), + false => term::normal_color(), + }; + let sized_string = self.sized_string(left); + let padding = xsize - sized_string.width() as u16; + + format!( + "{}{}{:padding$}{}{}{}", + text_color, + sized_string, + " ", + term::highlight_color(), + term::cursor_left(right.width()), + right, + padding = padding as usize + ) + } + // fn add_highlight(&self, line: &str) -> String { + // line.to_string() + // } + fn render_header(&self) -> String; + fn sized_string(&self, string: &str) -> String { + let (xsize, _) = self.get_dimensions(); + let lenstr: String = string.chars().fold("".into(), |acc,ch| { + if acc.width() + 1 >= xsize as usize { acc } + else { acc + &ch.to_string() } + }); + lenstr + } + + + fn on_key(&mut self, key: Key) { + match key { + _ => { + self.bad(Event::Key(key)) + } + } + } + + fn on_mouse(&mut self, event: MouseEvent) { + match event { + _ => { + self.bad(Event::Mouse(event)) + } + } + } + + fn on_wtf(&mut self, event: Vec) { + match event { + _ => { + self.bad(Event::Unsupported(event)) + } + } + } + + + + fn show_status(&mut self, status: &str) { + crate::window::show_status(status); + } + + fn bad(&mut self, event: Event) { + self.show_status(&format!("Stop the nasty stuff!! {:?} does nothing!", event)); + } + + //fn get_window(&self) -> Window; + //fn get_window_mut(&mut self) -> &mut Window; + + //fn run(&mut self) { + // self.draw(); + // self.handle_input(); + //} + + + //fn get_buffer(&self) -> &Vec; + fn refresh(&mut self); + fn get_drawlist(&mut self) -> String; +} diff --git a/src/win_main.rs b/src/win_main.rs new file mode 100644 index 0000000..f084b83 --- /dev/null +++ b/src/win_main.rs @@ -0,0 +1,49 @@ +//use crate::listview::ListView; + + +// struct MainWindow { +// active_widget: usize, +// main: ListView, +// parent: ListView, +// child: ListView +// } + +// impl Widget for ListView +// where +// Files: std::ops::Index +// { +// // fn go(&mut self) { +// // let pos = self.current_selection(); +// // let name = &self.content.content[pos].name.clone(); +// // let path = &self.content.content[pos].path.clone(); +// // let newfiles = crate::files::get_files(path).unwrap(); + +// // let listview = ListView::new(newfiles, (80,80), (10,10)); + +// // let mut win = Window::new(listview); +// // win.draw(); +// // win.handle_input(); +// // } +// } + +// impl Renderable for Window> { +// fn get_dimensions(&self) -> (u16, u16) { +// self.content.get_dimensions() +// } +// fn get_position(&self) -> (u16, u16) { +// self.content.get_position() +// } +// fn render(&self) -> Vec { +// self.content.render() +// } +// fn render_header(&self) -> String { +// self.content.render_header() +// } +// } + +// impl Window> { +// pub fn run(&mut self) { +// self.draw(); +// self.handle_input(); +// } +// } diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..51368dd --- /dev/null +++ b/src/window.rs @@ -0,0 +1,178 @@ +use std::cell::RefCell; +use std::io::{stdin, stdout, Stdout, Write}; +use std::process::exit; +use std::rc::*; +use std::sync::{Arc, Mutex}; + +use termion::event::{Event, Key}; +use termion::input::TermRead; +use termion::screen::AlternateScreen; + +use crate::term; +use crate::term::ScreenExt; + +use crate::widget::Widget; + +pub struct Window +where T: Widget +{ + pub selection: usize, + pub widget: T, + pub status: Arc>>, + pub screen: AlternateScreen>, + pub dimensions: (u16, u16), +} + +pub const HEADER_MARGIN: usize = 1; +pub const STATUS_BAR_MARGIN: usize = 2; + +impl Window +where + T: Widget +{ + pub fn new(widget: T) -> Window { + let mut screen = AlternateScreen::from(Box::new(stdout())); + screen.cursor_hide(); + let mut win = Window:: { + selection: 0, + widget: widget, + status: STATUS_BAR_CONTENT.clone(), + screen: screen, + dimensions: termion::terminal_size().unwrap(), + }; + win.widget.refresh(); + win + } + + pub fn run(&mut self) { + self.draw(); + self.handle_input(); + } + + pub fn draw(&mut self) { + let output = self.widget.get_drawlist(); + self.screen.write(output.as_ref()).unwrap(); + + self.screen.flush().unwrap(); + Self::draw_status(); + } + + pub fn show_status(status: &str) { + show_status(status); + } + + pub fn draw_status() { + draw_status(); + } + + pub fn clear_status() { + Self::show_status(""); + } + + pub fn minibuffer(&mut self, query: &str) -> Option { + Self::show_status(&(query.to_string() + ": ")); + let reply = Rc::new(RefCell::new(String::new())); + + for key in stdin().events() { + let key = key.unwrap(); + match key { + Event::Key(Key::Esc) => { + return None; + } + Event::Key(Key::Char('\n')) => { + if reply.borrow().len() == 0 { + return None; + } else { + return Some(reply.borrow().to_string()); + } + } + Event::Key(Key::Char(c)) => { + reply.borrow_mut().push(c); + } + Event::Key(Key::Backspace) => { + reply.borrow_mut().pop(); + } + _ => {} + }; + Self::show_status(&(query.to_string() + ": " + &reply.borrow())); + } + None + } + + pub fn quit(&mut self) { + panic!("It's your fault!"); + } + + pub fn handle_input(&mut self) { + self.draw(); + for event in stdin().events() { + Self::clear_status(); + self.draw(); + let event = event.unwrap(); + match event { + Event::Key(Key::Char('q')) => { + self.quit(); + } + Event::Key(Key::Left) => { + return; + } + Event::Key(key) => { + self.widget.on_key(key); + } + Event::Mouse(button) => { + self.widget.on_mouse(button); + } + Event::Unsupported(value) => { + self.widget.on_wtf(value); + } + } + self.draw(); + } + } +} + +impl Drop for Window +where + T: Widget +{ + fn drop(&mut self) { + // When done, restore the defaults to avoid messing with the terminal. + self.screen.write(format!("{}{}{}{}{}", + termion::screen::ToMainScreen, + termion::clear::All, + termion::style::Reset, + termion::cursor::Show, + termion::cursor::Goto(1, 1)).as_ref()).unwrap(); + } +} + +lazy_static! { + static ref STATUS_BAR_CONTENT: Arc>> = Arc::new(Mutex::new(None)); +} + +pub fn draw_status() { + let xsize = term::xsize() as u16; + let status = STATUS_BAR_CONTENT.try_lock().unwrap().clone(); + + status.or(Some("".to_string())).and_then(|status| { + write!( + stdout(), + "{}{}{:xsize$}{}{}", + term::move_bottom(), + term::status_bg(), + " ", + term::move_bottom(), + status, + xsize = xsize as usize + ).ok() + }); + stdout().flush().unwrap(); +} + +pub fn show_status(status: &str) { + { + let mut status_content = STATUS_BAR_CONTENT.try_lock().unwrap(); + *status_content = Some(status.to_string()); + } + draw_status(); +}