First commit, did some refactoring around widgets, etc, etc

This commit is contained in:
rabite 2019-01-21 14:44:34 +01:00
commit 67c973c0af
9 changed files with 697 additions and 0 deletions

11
Cargo.toml Normal file
View File

@ -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 = "*"

62
src/files.rs Normal file
View File

@ -0,0 +1,62 @@
use std::ops::Index;
pub struct Files(Vec<File>);
impl Index<usize> for Files {
type Output = File;
fn index(&self, pos: usize) -> &Self::Output {
&self.0[pos]
}
}
impl Files {
pub fn iter(&self) -> std::slice::Iter<File> {
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<usize>,
// owner: Option<String>,
// group: Option<String>,
// flags: Option<String>,
// ctime: Option<u32>,
// mtime: Option<u32>,
}
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<Files, std::io::Error> {
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))
}

51
src/hbox.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::widget::Widget;
pub struct HBox {
dimensions: (u16, u16),
position: (u16, u16),
children: Vec<Box<Widget>>,
main: usize
}
impl HBox {
pub fn new(widgets: Vec<Box<Widget>>) -> HBox {
HBox {
dimensions: (100, 100),
position: (1, 1),
children: widgets,
main: 0
}
}
}
impl Widget for HBox {
fn render(&self) -> Vec<String> {
// 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
}
}

125
src/listview.rs Normal file
View File

@ -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<T> {
pub content: T,
selection: usize,
offset: usize,
buffer: Vec<String>,
dimensions: (u16, u16),
position: (u16, u16),
}
impl<T: 'static> ListView<T> where ListView<T>: Widget {
pub fn new(content: T, dimensions: (u16, u16), position: (u16, u16)) -> Self {
let view = ListView::<T> {
content: content,
selection: 0,
offset: 0,
buffer: Vec::new(),
dimensions: dimensions,
position: position
};
view
}
pub fn to_trait(self) -> Box<Widget> {
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<Files> {
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<String> {
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(),
_ => {}
}
}
}

46
src/main.rs Normal file
View File

@ -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();
}

84
src/term.rs Normal file
View File

@ -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<Box<Stdout>> {}
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))
}

91
src/widget.rs Normal file
View File

@ -0,0 +1,91 @@
use termion::event::{Key, MouseEvent, Event};
use unicode_width::{UnicodeWidthStr};
use crate::term;
pub trait Widget {
fn render(&self) -> Vec<String>;
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<u8>) {
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<Widget>;
//fn get_window_mut(&mut self) -> &mut Window<dyn Widget>;
//fn run(&mut self) {
// self.draw();
// self.handle_input();
//}
//fn get_buffer(&self) -> &Vec<String>;
fn refresh(&mut self);
fn get_drawlist(&mut self) -> String;
}

49
src/win_main.rs Normal file
View File

@ -0,0 +1,49 @@
//use crate::listview::ListView;
// struct MainWindow {
// active_widget: usize,
// main: ListView<Files>,
// parent: ListView<Files>,
// child: ListView<Files>
// }
// impl Widget for ListView<Files>
// where
// Files: std::ops::Index<usize>
// {
// // 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<ListView<Files>> {
// fn get_dimensions(&self) -> (u16, u16) {
// self.content.get_dimensions()
// }
// fn get_position(&self) -> (u16, u16) {
// self.content.get_position()
// }
// fn render(&self) -> Vec<String> {
// self.content.render()
// }
// fn render_header(&self) -> String {
// self.content.render_header()
// }
// }
// impl Window<ListView<Files>> {
// pub fn run(&mut self) {
// self.draw();
// self.handle_input();
// }
// }

178
src/window.rs Normal file
View File

@ -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<T>
where T: Widget
{
pub selection: usize,
pub widget: T,
pub status: Arc<Mutex<Option<String>>>,
pub screen: AlternateScreen<Box<Stdout>>,
pub dimensions: (u16, u16),
}
pub const HEADER_MARGIN: usize = 1;
pub const STATUS_BAR_MARGIN: usize = 2;
impl<T> Window<T>
where
T: Widget
{
pub fn new(widget: T) -> Window<T> {
let mut screen = AlternateScreen::from(Box::new(stdout()));
screen.cursor_hide();
let mut win = Window::<T> {
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<String> {
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<T> Drop for Window<T>
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<Mutex<Option<String>>> = 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();
}