mirror of https://github.com/bobwen-dev/hunter
First commit, did some refactoring around widgets, etc, etc
This commit is contained in:
commit
67c973c0af
|
@ -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 = "*"
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
// }
|
||||
// }
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue