diff options
author | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-02-22 12:59:13 -0500 |
---|---|---|
committer | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-02-22 13:32:02 -0500 |
commit | 03594099dafb4cda04e50f087df61cf76e2034d0 (patch) | |
tree | 724d31c9b1d31d122d1862141fdc9391891821e4 /src/ui | |
parent | b3ed647b033c079a614e7a9ff5bb88da14dd99b4 (diff) |
move the majority of rendering into its own widget: TuiView
- textfield is now a widget as well
- reduced code duplication with TuiView
- add backtab support
- add a message queue for notifications
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/tui_backend.rs | 79 | ||||
-rw-r--r-- | src/ui/widgets/mod.rs | 6 | ||||
-rw-r--r-- | src/ui/widgets/tui_dirlist.rs | 36 | ||||
-rw-r--r-- | src/ui/widgets/tui_dirlist_detailed.rs | 48 | ||||
-rw-r--r-- | src/ui/widgets/tui_footer.rs | 10 | ||||
-rw-r--r-- | src/ui/widgets/tui_menu.rs | 146 | ||||
-rw-r--r-- | src/ui/widgets/tui_textfield.rs | 86 | ||||
-rw-r--r-- | src/ui/widgets/tui_topbar.rs | 4 | ||||
-rw-r--r-- | src/ui/widgets/tui_view.rs | 74 |
9 files changed, 266 insertions, 223 deletions
diff --git a/src/ui/tui_backend.rs b/src/ui/tui_backend.rs index 4c64497..446d1c0 100644 --- a/src/ui/tui_backend.rs +++ b/src/ui/tui_backend.rs @@ -1,22 +1,12 @@ -use std::io::Write; - -use tui::buffer::Buffer; use termion::raw::{IntoRawMode, RawTerminal}; use termion::screen::AlternateScreen; use tui::backend::TermionBackend; -use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::widgets::Widget; -use unicode_width::UnicodeWidthStr; - -use super::widgets::{TuiDirList, TuiDirListDetailed, TuiFooter, TuiTopBar}; -use crate::context::JoshutoContext; pub struct TuiBackend { pub terminal: tui::Terminal<TermionBackend<AlternateScreen<RawTerminal<std::io::Stdout>>>>, } -use super::{DEFAULT_LAYOUT, NO_PREVIEW_LAYOUT}; - impl TuiBackend { pub fn new() -> std::io::Result<Self> { let stdout = std::io::stdout().into_raw_mode()?; @@ -27,70 +17,13 @@ impl TuiBackend { Ok(Self { terminal }) } - pub fn render(&mut self, context: &JoshutoContext) { - let curr_tab = context.curr_tab_ref(); - - let curr_list = curr_tab.curr_list_ref(); - let parent_list = curr_tab.parent_list_ref(); - let child_list = curr_tab.child_list_ref(); - - let f_size = { - let frame = self.terminal.get_frame(); - frame.size() - }; - + pub fn render<W>(&mut self, widget: &mut W) + where + W: Widget, + { self.terminal.draw(|mut frame| { - let f_size = frame.size(); - - { - let top_rect = Rect { - x: 0, - y: 0, - width: f_size.width, - height: 1, - }; - - TuiTopBar::new(curr_tab.curr_path.as_path()) - .render(&mut frame, top_rect); - } - - let constraints = match child_list { - Some(_) => DEFAULT_LAYOUT, - None => NO_PREVIEW_LAYOUT, - }; - let layout_rect = Layout::default() - .direction(Direction::Horizontal) - .margin(1) - .constraints(constraints.as_ref()) - .split(f_size); - - if let Some(curr_list) = parent_list.as_ref() { - TuiDirList::new(&curr_list).render(&mut frame, layout_rect[0]); - }; - - if let Some(curr_list) = curr_list.as_ref() { - TuiDirListDetailed::new(&curr_list).render(&mut frame, layout_rect[1]); - if let Some(entry) = curr_list.get_curr_ref() { - let top_rect = Rect { - x: 0, - y: f_size.height - 1, - width: f_size.width, - height: 1, - }; - TuiFooter::new(entry) - .render(&mut frame, top_rect); - } - }; - - if let Some(curr_list) = child_list.as_ref() { - TuiDirList::new(&curr_list).render(&mut frame, layout_rect[2]); - }; + let rect = frame.size(); + widget.render(&mut frame, rect); }); } } - -impl Widget for TuiBackend { - fn draw(&mut self, area: Rect, buf: &mut Buffer) { - - } -} diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index d80d4ca..e36c904 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -2,10 +2,14 @@ pub mod tui_dirlist; pub mod tui_dirlist_detailed; pub mod tui_footer; pub mod tui_menu; +pub mod tui_textfield; pub mod tui_topbar; +pub mod tui_view; pub use self::tui_dirlist::TuiDirList; pub use self::tui_dirlist_detailed::TuiDirListDetailed; pub use self::tui_footer::TuiFooter; -pub use self::tui_menu::TuiCommandMenu; +pub use self::tui_menu::{TuiCommandMenu, TuiMenu}; +pub use self::tui_textfield::TuiTextField; pub use self::tui_topbar::TuiTopBar; +pub use self::tui_view::TuiView; diff --git a/src/ui/widgets/tui_dirlist.rs b/src/ui/widgets/tui_dirlist.rs index 77684c7..4992fab 100644 --- a/src/ui/widgets/tui_dirlist.rs +++ b/src/ui/widgets/tui_dirlist.rs @@ -3,10 +3,8 @@ use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; use tui::widgets::Widget; use unicode_width::UnicodeWidthStr; -use unicode_width::UnicodeWidthChar; use crate::fs::JoshutoDirList; -use crate::util::format; pub struct TuiDirList<'a> { dirlist: &'a JoshutoDirList, @@ -67,40 +65,32 @@ impl<'a> Widget for TuiDirList<'a> { let file_type = entry.metadata.file_type; if file_type.is_dir() { if name_width <= area_width { - buf.set_stringn(x, y + i as u16, - name, - area_width, style); + buf.set_stringn(x, y + i as u16, name, area_width, style); } else { - buf.set_stringn(x, y + i as u16, - name, - area_width - 1, style); - buf.set_string(x + area_width as u16 - 1, y + i as u16, - "…", style); + buf.set_stringn(x, y + i as u16, name, area_width - 1, style); + buf.set_string(x + area_width as u16 - 1, y + i as u16, "…", style); } continue; } if name_width < area_width { - buf.set_stringn(x, y + i as u16, - name, - area_width, style); + buf.set_stringn(x, y + i as u16, name, area_width, style); } else { match name.rfind('.') { None => { - buf.set_stringn(x, y + i as u16, - name, - area_width, style); + buf.set_stringn(x, y + i as u16, name, area_width, style); } Some(p_ind) => { let ext_width = name[p_ind..].width(); let file_name_width = area_width - ext_width - 1; - buf.set_stringn(x, y + i as u16, - &name[..p_ind], - file_name_width, style); - buf.set_string(x + file_name_width as u16, y + i as u16, - "…", style); - buf.set_string(x + file_name_width as u16 + 1, y + i as u16, - &name[p_ind..], style); + buf.set_stringn(x, y + i as u16, &name[..p_ind], file_name_width, style); + buf.set_string(x + file_name_width as u16, y + i as u16, "…", style); + buf.set_string( + x + file_name_width as u16 + 1, + y + i as u16, + &name[p_ind..], + style, + ); } } } diff --git a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs index 2e01a26..d0f9c12 100644 --- a/src/ui/widgets/tui_dirlist_detailed.rs +++ b/src/ui/widgets/tui_dirlist_detailed.rs @@ -2,8 +2,8 @@ use tui::buffer::Buffer; use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; use tui::widgets::Widget; -use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthChar; +use unicode_width::UnicodeWidthStr; use crate::fs::JoshutoDirList; use crate::util::format; @@ -35,9 +35,7 @@ impl<'a> Widget for TuiDirListDetailed<'a> { let dir_len = self.dirlist.contents.len(); if dir_len == 0 { - let style = Style::default() - .bg(Color::Red) - .fg(Color::White); + let style = Style::default().bg(Color::Red).fg(Color::White); buf.set_stringn(x, y, "empty", area.width as usize, style); return; } @@ -71,47 +69,43 @@ impl<'a> Widget for TuiDirListDetailed<'a> { let file_type = entry.metadata.file_type; if file_type.is_dir() { if name_width <= area_width { - buf.set_stringn(x, y + i as u16, - name, - area_width, style); + buf.set_stringn(x, y + i as u16, name, area_width, style); } else { - buf.set_stringn(x, y + i as u16, - name, - area_width - 1, style); - buf.set_string(x + area_width as u16 - 1, y + i as u16, - "…", style); + buf.set_stringn(x, y + i as u16, name, area_width - 1, style); + buf.set_string(x + area_width as u16 - 1, y + i as u16, "…", style); } continue; } if name_width < area_width - FILE_SIZE_WIDTH { - buf.set_stringn(x, y + i as u16, - name, - area_width - FILE_SIZE_WIDTH, style); + buf.set_stringn(x, y + i as u16, name, area_width - FILE_SIZE_WIDTH, style); } else { match name.rfind('.') { None => { - buf.set_stringn(x, y + i as u16, - name, - area_width - FILE_SIZE_WIDTH, style); + buf.set_stringn(x, y + i as u16, name, area_width - FILE_SIZE_WIDTH, style); } Some(p_ind) => { let ext_width = name[p_ind..].width(); let file_name_width = area_width - FILE_SIZE_WIDTH - ext_width - 2; - buf.set_stringn(x, y + i as u16, - &name[..p_ind], - file_name_width, style); - buf.set_string(x + file_name_width as u16, y + i as u16, - "…", style); - buf.set_string(x + file_name_width as u16 + 1, y + i as u16, - &name[p_ind..], style); + buf.set_stringn(x, y + i as u16, &name[..p_ind], file_name_width, style); + buf.set_string(x + file_name_width as u16, y + i as u16, "…", style); + buf.set_string( + x + file_name_width as u16 + 1, + y + i as u16, + &name[p_ind..], + style, + ); } } } let file_size_string = format::file_size_to_string(entry.metadata.len as f64); - buf.set_string(x + (area_width - FILE_SIZE_WIDTH) as u16, y + i as u16, - file_size_string, style); + buf.set_string( + x + (area_width - FILE_SIZE_WIDTH) as u16, + y + i as u16, + file_size_string, + style, + ); } } } diff --git a/src/ui/widgets/tui_footer.rs b/src/ui/widgets/tui_footer.rs index ac34b98..8bc1b49 100644 --- a/src/ui/widgets/tui_footer.rs +++ b/src/ui/widgets/tui_footer.rs @@ -1,5 +1,4 @@ use std::fs; -use std::path::Path; use tui::buffer::Buffer; use tui::layout::Rect; @@ -9,8 +8,6 @@ use tui::widgets::{Paragraph, Text, Widget}; use crate::fs::JoshutoDirEntry; use crate::util::format; -use crate::{HOSTNAME, USERNAME}; - pub struct TuiFooter<'a> { entry: &'a JoshutoDirEntry, } @@ -28,8 +25,7 @@ impl<'a> Widget for TuiFooter<'a> { let mode = self.entry.metadata.permissions.mode(); let mode = format::mode_to_string(mode); - let mode_style = Style::default() - .fg(Color::Cyan); + let mode_style = Style::default().fg(Color::Cyan); let mtime = self.entry.metadata.modified; let mtime = format::mtime_to_string(mtime); @@ -54,8 +50,6 @@ impl<'a> Widget for TuiFooter<'a> { } } - Paragraph::new(text.iter()) - .wrap(true) - .draw(area, buf); + Paragraph::new(text.iter()).wrap(true).draw(area, buf); } } diff --git a/src/ui/widgets/tui_menu.rs b/src/ui/widgets/tui_menu.rs index 64100f4..9dde1fe 100644 --- a/src/ui/widgets/tui_menu.rs +++ b/src/ui/widgets/tui_menu.rs @@ -1,28 +1,18 @@ -use std::io::{self, Write}; use std::iter::Iterator; -use tui::buffer::Buffer; -use termion::clear; -use termion::cursor::Goto; use termion::event::Key; -use termion::raw::IntoRawMode; -use termion::screen::AlternateScreen; -use tui::backend::TermionBackend; -use tui::layout::{Constraint, Direction, Layout, Rect}; +use tui::buffer::Buffer; +use tui::layout::Rect; use tui::style::{Color, Style}; -use tui::widgets::{Block, Borders, List, Paragraph, Text, Widget}; -use tui::Terminal; +use tui::widgets::{Block, Borders, Widget}; use unicode_width::UnicodeWidthStr; -use crate::commands::{CommandKeybind, CursorMoveUp, JoshutoCommand, JoshutoRunnable}; +use super::TuiView; +use crate::commands::{CommandKeybind, JoshutoCommand}; use crate::config::JoshutoCommandMapping; use crate::context::JoshutoContext; use crate::ui::TuiBackend; -use crate::util::event::{Event, Events}; -use super::{TuiDirList, TuiDirListDetailed, TuiTopBar}; - -use crate::{HOSTNAME, USERNAME}; -use super::super::{DEFAULT_LAYOUT, NO_PREVIEW_LAYOUT}; +use crate::util::event::Event; const BORDER_HEIGHT: usize = 1; const BOTTOM_MARGIN: usize = 1; @@ -34,76 +24,43 @@ impl TuiCommandMenu { Self {} } - pub fn get_input<'a>(&mut self, backend: &mut TuiBackend, - context: &JoshutoContext, m: &'a JoshutoCommandMapping) -> - Option<&'a Box<JoshutoCommand>> { + pub fn get_input<'a>( + &mut self, + backend: &mut TuiBackend, + context: &JoshutoContext, + m: &'a JoshutoCommandMapping, + ) -> Option<&'a Box<dyn JoshutoCommand>> { let mut map: &JoshutoCommandMapping = &m; - let events = &context.events; - - let curr_tab = context.curr_tab_ref(); - - let curr_list = curr_tab.curr_list_ref(); - let parent_list = curr_tab.parent_list_ref(); - let child_list = curr_tab.child_list_ref(); loop { backend.terminal.draw(|mut frame| { let f_size = frame.size(); { - let top_rect = Rect { - x: 0, - y: 0, - width: f_size.width, - height: 1, - }; - - TuiTopBar::new(curr_tab.curr_path.as_path()) - .render(&mut frame, top_rect); + let mut view = TuiView::new(&context); + view.render(&mut frame, f_size); } - let constraints = match child_list { - Some(_) => DEFAULT_LAYOUT, - None => NO_PREVIEW_LAYOUT, - }; - let layout_rect = Layout::default() - .direction(Direction::Horizontal) - .margin(1) - .constraints(constraints.as_ref()) - .split(f_size); - - if let Some(curr_list) = parent_list.as_ref() { - TuiDirList::new(&curr_list).render(&mut frame, layout_rect[0]); - }; - - if let Some(curr_list) = curr_list.as_ref() { - TuiDirListDetailed::new(&curr_list).render(&mut frame, layout_rect[1]); - }; - - if let Some(curr_list) = child_list.as_ref() { - TuiDirList::new(&curr_list).render(&mut frame, layout_rect[2]); - }; - { // draw menu let mut display_vec: Vec<String> = map .iter() - .map(|(k, v)| { - format!(" {:?} {}", k, v) - }) + .map(|(k, v)| format!(" {:?} {}", k, v)) .collect(); display_vec.sort(); - let display_str: Vec<&str> = - display_vec.iter().map(|v| v.as_str()).collect(); + let display_str: Vec<&str> = display_vec.iter().map(|v| v.as_str()).collect(); let display_str_len = display_str.len(); - let y = if (f_size.height as usize) < display_str_len + BORDER_HEIGHT + BOTTOM_MARGIN { - 0 - } else { - f_size.height - (BORDER_HEIGHT + BOTTOM_MARGIN) as u16 - - display_str_len as u16 - }; + let y = if (f_size.height as usize) + < display_str_len + BORDER_HEIGHT + BOTTOM_MARGIN + { + 0 + } else { + f_size.height + - (BORDER_HEIGHT + BOTTOM_MARGIN) as u16 + - display_str_len as u16 + }; let menu_rect = Rect { x: 0, @@ -115,27 +72,24 @@ impl TuiCommandMenu { TuiMenu::new(&display_str).render(&mut frame, menu_rect); } }); - if let Ok(event) = events.next() { + if let Ok(event) = context.events.next() { match event { Event::Input(Key::Esc) => { return None; } - Event::Input(key) => { - match map.get(&key) { - Some(CommandKeybind::SimpleKeybind(s)) => { - return Some(s); - } - Some(CommandKeybind::CompositeKeybind(m)) => { - map = m; - } - None => return None, + Event::Input(key) => match map.get(&key) { + Some(CommandKeybind::SimpleKeybind(s)) => { + return Some(s); } - } - _ => {}, + Some(CommandKeybind::CompositeKeybind(m)) => { + map = m; + } + None => return None, + }, + _ => {} } } } - } } @@ -149,14 +103,30 @@ impl<'a> TuiMenu<'a> { } } +const LONG_SPACE: &str = " "; + impl<'a> Widget for TuiMenu<'a> { fn draw(&mut self, area: Rect, buf: &mut Buffer) { - let text_iter = self.options.iter().map(|s| Text::raw(*s)); - let block = Block::default() - .borders(Borders::TOP); - - List::new(text_iter) - .block(block) - .draw(area, buf); + let text_iter = self.options.iter(); + let mut block = Block::default().borders(Borders::TOP); + + block.draw(area, buf); + + let style = Style::default(); + + let area_x = area.x + 1; + let area_y = area.y + 1; + + for (i, text) in text_iter.enumerate() { + let width = text.width(); + buf.set_stringn(area_x, area_y + i as u16, text, width, style); + buf.set_stringn( + area_x + width as u16, + area_y + i as u16, + LONG_SPACE, + area.width as usize, + style, + ); + } } } diff --git a/src/ui/widgets/tui_textfield.rs b/src/ui/widgets/tui_textfield.rs new file mode 100644 index 0000000..c0f54a8 --- /dev/null +++ b/src/ui/widgets/tui_textfield.rs @@ -0,0 +1,86 @@ +use rustyline::completion::{Candidate, Completer, FilenameCompleter, Pair}; +use rustyline::line_buffer; + +use termion::clear; +use termion::cursor::Goto; +use termion::event::Key; +use tui::layout::Rect; +use tui::style::{Color, Style}; +use tui::widgets::{Block, Borders, List, Paragraph, Text, Widget}; +use unicode_width::UnicodeWidthStr; + +use crate::context::JoshutoContext; +use crate::ui::TuiBackend; +use crate::util::event::{Event, Events}; + +use super::TuiMenu; + +struct CompletionTracker { + pub index: usize, + pub pos: usize, + pub original: String, + pub candidates: Vec<Pair>, +} + +impl CompletionTracker { + pub fn new(pos: usize, candidates: Vec<Pair>, original: String) -> Self { + CompletionTracker { + index: 0, + pos, + original, + candidates, + } + } +} + +pub struct TuiTextField<'a> { + menu: Option<&'a mut TuiMenu<'a>>, +} + +impl<'a> TuiTextField<'a> { + pub fn new(menu: &'a mut TuiMenu<'a>) -> Self { + Self { menu: Some(menu) } + } + + pub fn get_input( + &mut self, + backend: &mut TuiBackend, + context: &JoshutoContext, + ) -> Option<String> { + let mut input_string = String::with_capacity(64); + + loop { + backend.terminal.draw(|mut frame| { + let f_size = frame.size(); + + let top_rect = Rect { + x: 0, + y: 0, + width: f_size.width, + height: 1, + }; + + if let Some(menu) = self.menu.as_mut() { + menu.render(&mut frame, top_rect); + } + }); + + if let Ok(event) = context.events.next() { + match event { + Event::Input(Key::Esc) => { + return None; + } + Event::Input(Key::Char('\n')) => { + break; + } + Event::Input(Key::Char(c)) => { + input_string.push(c); + } + _ => {} + }; + } + } + eprintln!("You typed: {}", input_string); + Some(input_string) + } +} diff --git a/src/ui/widgets/tui_topbar.rs b/src/ui/widgets/tui_topbar.rs index 6ff46b7..e87a3e5 100644 --- a/src/ui/widgets/tui_topbar.rs +++ b/src/ui/widgets/tui_topbar.rs @@ -34,8 +34,6 @@ impl<'a> Widget for TuiTopBar<'a> { Text::styled(curr_path_str, path_style), ]; - Paragraph::new(text.iter()) - .wrap(true) - .draw(area, buf); + Paragraph::new(text.iter()).wrap(true).draw(area, buf); } } diff --git a/src/ui/widgets/tui_view.rs b/src/ui/widgets/tui_view.rs new file mode 100644 index 0000000..57f644c --- /dev/null +++ b/src/ui/widgets/tui_view.rs @@ -0,0 +1,74 @@ +use tui::buffer::Buffer; +use tui::layout::{Direction, Layout, Rect}; +use tui::widgets::Widget; +use unicode_width::UnicodeWidthStr; + +use super::{TuiDirList, TuiDirListDetailed, TuiFooter, TuiTopBar}; +use crate::context::JoshutoContext; + +pub struct TuiView<'a> { + pub context: &'a JoshutoContext, +} + +use super::super::{DEFAULT_LAYOUT, NO_PREVIEW_LAYOUT}; + +impl<'a> TuiView<'a> { + pub fn new(context: &'a JoshutoContext) -> Self { + Self { context } + } +} + +impl<'a> Widget for TuiView<'a> { + fn draw(&mut self, area: Rect, buf: &mut Buffer) { + let curr_tab = self.context.curr_tab_ref(); + + let curr_list = curr_tab.curr_list_ref(); + let parent_list = curr_tab.parent_list_ref(); + let child_list = curr_tab.child_list_ref(); + + let f_size = area; + + let constraints = match child_list { + Some(_) => DEFAULT_LAYOUT, + None => NO_PREVIEW_LAYOUT, + }; + let layout_rect = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints(constraints.as_ref()) + .split(f_size); + + { + let rect = Rect { + x: 0, + y: 0, + width: f_size.width, + height: 1, + }; + + TuiTopBar::new(curr_tab.curr_path.as_path()).draw(rect, buf); + } + + if let Some(curr_list) = parent_list.as_ref() { + TuiDirList::new(&curr_list).draw(layout_rect[0], buf); + }; + + if let Some(curr_list) = curr_list.as_ref() { + TuiDirListDetailed::new(&curr_list).draw(layout_rect[1], buf); + + if let Some(entry) = curr_list.get_curr_ref() { + let rect = Rect { + x: 0, + y: f_size.height - 1, + width: f_size.width, + height: 1, + }; + TuiFooter::new(entry).draw(rect, buf); + } + }; + + if let Some(curr_list) = child_list.as_ref() { + TuiDirList::new(&curr_list).draw(layout_rect[2], buf); + }; + } +} |