summaryrefslogtreecommitdiffstats
path: root/src/ui/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/views')
-rw-r--r--src/ui/views/mod.rs4
-rw-r--r--src/ui/views/tui_command_menu.rs101
-rw-r--r--src/ui/views/tui_textfield.rs242
3 files changed, 347 insertions, 0 deletions
diff --git a/src/ui/views/mod.rs b/src/ui/views/mod.rs
index e6b1c41..70517cf 100644
--- a/src/ui/views/mod.rs
+++ b/src/ui/views/mod.rs
@@ -1,7 +1,11 @@
+mod tui_command_menu;
mod tui_folder_view;
+mod tui_textfield;
mod tui_view;
mod tui_worker_view;
+pub use self::tui_command_menu::TuiCommandMenu;
pub use self::tui_folder_view::TuiFolderView;
+pub use self::tui_textfield::TuiTextField;
pub use self::tui_view::TuiView;
pub use self::tui_worker_view::TuiWorkerView;
diff --git a/src/ui/views/tui_command_menu.rs b/src/ui/views/tui_command_menu.rs
new file mode 100644
index 0000000..fa2e857
--- /dev/null
+++ b/src/ui/views/tui_command_menu.rs
@@ -0,0 +1,101 @@
+use std::iter::Iterator;
+
+use termion::event::{Event, Key};
+use tui::layout::Rect;
+use tui::widgets::Clear;
+
+use crate::commands::{CommandKeybind, KeyCommand};
+use crate::config::JoshutoCommandMapping;
+use crate::context::JoshutoContext;
+use crate::ui::views::TuiView;
+use crate::ui::widgets::TuiMenu;
+use crate::ui::TuiBackend;
+use crate::util::event::JoshutoEvent;
+use crate::util::input;
+use crate::util::to_string::ToString;
+
+const BORDER_HEIGHT: usize = 1;
+const BOTTOM_MARGIN: usize = 1;
+
+pub struct TuiCommandMenu;
+
+impl TuiCommandMenu {
+ pub fn new() -> Self {
+ Self {}
+ }
+
+ pub fn get_input<'a>(
+ &mut self,
+ backend: &mut TuiBackend,
+ context: &mut JoshutoContext,
+ m: &'a JoshutoCommandMapping,
+ ) -> Option<&'a KeyCommand> {
+ let mut map: &JoshutoCommandMapping = &m;
+ let terminal = backend.terminal_mut();
+ context.flush_event();
+
+ loop {
+ let _ = terminal.draw(|frame| {
+ let f_size: Rect = frame.size();
+
+ {
+ let view = TuiView::new(&context);
+ frame.render_widget(view, f_size);
+ }
+
+ {
+ // draw menu
+ let mut display_vec: Vec<String> = map
+ .as_ref()
+ .iter()
+ .map(|(k, v)| format!(" {} {}", k.to_string(), v))
+ .collect();
+ display_vec.sort();
+ 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 menu_rect = Rect {
+ x: 0,
+ y,
+ width: f_size.width,
+ height: (display_str_len + BORDER_HEIGHT) as u16,
+ };
+
+ frame.render_widget(Clear, menu_rect);
+ frame.render_widget(TuiMenu::new(&display_str), menu_rect);
+ }
+ });
+
+ if let Ok(event) = context.poll_event() {
+ match event {
+ JoshutoEvent::Termion(event) => {
+ match event {
+ Event::Key(Key::Esc) => return None,
+ event => match map.as_ref().get(&event) {
+ Some(CommandKeybind::SimpleKeybind(s)) => {
+ return Some(s);
+ }
+ Some(CommandKeybind::CompositeKeybind(m)) => {
+ map = m;
+ }
+ None => return None,
+ },
+ }
+ context.flush_event();
+ }
+ event => input::process_noninteractive(event, context),
+ }
+ }
+ }
+ }
+}
diff --git a/src/ui/views/tui_textfield.rs b/src/ui/views/tui_textfield.rs
new file mode 100644
index 0000000..db1bc6f
--- /dev/null
+++ b/src/ui/views/tui_textfield.rs
@@ -0,0 +1,242 @@
+use rustyline::completion::{Candidate, Completer, FilenameCompleter, Pair};
+use rustyline::line_buffer;
+
+use termion::event::{Event, Key};
+use tui::layout::Rect;
+use tui::widgets::Clear;
+
+use crate::context::JoshutoContext;
+use crate::ui::views::TuiView;
+use crate::ui::widgets::{TuiMenu, TuiMultilineText};
+use crate::ui::TuiBackend;
+use crate::util::event::JoshutoEvent;
+use crate::util::input;
+
+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> {
+ _prompt: &'a str,
+ _prefix: &'a str,
+ _suffix: &'a str,
+ _menu_items: Vec<&'a str>,
+}
+
+impl<'a> TuiTextField<'a> {
+ pub fn menu_items<I>(&mut self, items: I) -> &mut Self
+ where
+ I: Iterator<Item = &'a str>,
+ {
+ self._menu_items = items.collect();
+ self
+ }
+
+ pub fn prompt(&mut self, prompt: &'a str) -> &mut Self {
+ self._prompt = prompt;
+ self
+ }
+
+ pub fn prefix(&mut self, prefix: &'a str) -> &mut Self {
+ self._prefix = prefix;
+ self
+ }
+
+ pub fn suffix(&mut self, suffix: &'a str) -> &mut Self {
+ self._suffix = suffix;
+ self
+ }
+
+ pub fn get_input(
+ &mut self,
+ backend: &mut TuiBackend,
+ context: &mut JoshutoContext,
+ ) -> Option<String> {
+ context.flush_event();
+
+ let mut line_buffer = line_buffer::LineBuffer::with_capacity(255);
+ let completer = FilenameCompleter::new();
+
+ let mut completion_tracker: Option<CompletionTracker> = None;
+
+ let char_idx = self._prefix.chars().map(|c| c.len_utf8()).sum();
+
+ line_buffer.insert_str(0, self._suffix);
+ line_buffer.insert_str(0, self._prefix);
+ line_buffer.set_pos(char_idx);
+
+ let terminal = backend.terminal_mut();
+
+ loop {
+ terminal
+ .draw(|frame| {
+ let area: Rect = frame.size();
+ if area.height == 0 {
+ return;
+ }
+ {
+ let mut view = TuiView::new(&context);
+ view.show_bottom_status = false;
+ frame.render_widget(view, area);
+ }
+
+ let cursor_xpos = line_buffer.pos();
+
+ let area_width = area.width as usize;
+ let buffer_str = line_buffer.as_str();
+ let line_str = format!("{}{}", self._prompt, buffer_str);
+ let multiline =
+ TuiMultilineText::new(line_str.as_str(), area_width, Some(cursor_xpos));
+ let multiline_height = multiline.len();
+
+ {
+ let menu_widget = TuiMenu::new(self._menu_items.as_slice());
+ let menu_len = menu_widget.len();
+ let menu_y = if menu_len + 1 > area.height as usize {
+ 0
+ } else {
+ (area.height as usize - menu_len - 1) as u16
+ };
+
+ let menu_rect = Rect {
+ x: 0,
+ y: menu_y - multiline_height as u16,
+ width: area.width,
+ height: menu_len as u16 + 1,
+ };
+ frame.render_widget(Clear, menu_rect);
+ frame.render_widget(menu_widget, menu_rect);
+ }
+
+ let multiline_rect = Rect {
+ x: 0,
+ y: area.height - multiline_height as u16,
+ width: area.width,
+ height: multiline_height as u16,
+ };
+
+ frame.render_widget(Clear, multiline_rect);
+ frame.render_widget(multiline, multiline_rect);
+ })
+ .unwrap();
+
+ if let Ok(event) = context.poll_event() {
+ match event {
+ JoshutoEvent::Termion(Event::Key(key)) => {
+ match key {
+ Key::Backspace => {
+ if line_buffer.backspace(1) {
+ completion_tracker.take();
+ }
+ }
+ Key::Left => {
+ if line_buffer.move_backward(1) {
+ completion_tracker.take();
+ }
+ }
+ Key::Right => {
+ if line_buffer.move_forward(1) {
+ completion_tracker.take();
+ }
+ }
+ Key::Delete => {
+ if line_buffer.delete(1).is_some() {
+ completion_tracker.take();
+ }
+ }
+ Key::Home => {
+ line_buffer.move_home();
+ completion_tracker.take();
+ }
+ Key::End => {
+ line_buffer.move_end();
+ completion_tracker.take();
+ }
+ Key::Up => {}
+ Key::Down => {}
+ Key::Esc => {
+ return None;
+ }
+ Key::Char('\t') => {
+ if completion_tracker.is_none() {
+ let res = completer
+ .complete_path(line_buffer.as_str(), line_buffer.pos());
+ if let Ok((pos, mut candidates)) = res {
+ candidates.sort_by(|x, y| {
+ x.display()
+ .partial_cmp(y.display())
+ .unwrap_or(std::cmp::Ordering::Less)
+ });
+ let ct = CompletionTracker::new(
+ pos,
+ candidates,
+ String::from(line_buffer.as_str()),
+ );
+ completion_tracker = Some(ct);
+ }
+ }
+
+ if let Some(ref mut s) = completion_tracker {
+ if s.index < s.candidates.len() {
+ let candidate = &s.candidates[s.index];
+ completer.update(
+ &mut line_buffer,
+ s.pos,
+ candidate.display(),
+ );
+ s.index += 1;
+ }
+ }
+ }
+ Key::Char('\n') => {
+ break;
+ }
+ Key::Char(c) => {
+ if line_buffer.insert(c, 1).is_some() {
+ completion_tracker.take();
+ }
+ }
+ _ => {}
+ }
+ context.flush_event();
+ }
+ JoshutoEvent::Termion(_) => {
+ context.flush_event();
+ }
+ event => input::process_noninteractive(event, context),
+ };
+ }
+ }
+ if line_buffer.as_str().is_empty() {
+ None
+ } else {
+ let input_string = line_buffer.to_string();
+ Some(input_string)
+ }
+ }
+}
+
+impl<'a> std::default::Default for TuiTextField<'a> {
+ fn default() -> Self {
+ Self {
+ _prompt: "",
+ _prefix: "",
+ _suffix: "",
+ _menu_items: vec![],
+ }
+ }
+}