diff options
author | Jeff Zhao <jeff.no.zhao@gmail.com> | 2021-02-09 16:01:56 -0500 |
---|---|---|
committer | Jeff Zhao <jeff.no.zhao@gmail.com> | 2021-02-09 16:01:56 -0500 |
commit | 621bd8856f8e8e36ce9ff0351dedeff2a4734def (patch) | |
tree | 308335fe9560c7c178b1273be62d6942b33acef6 /src/ui/widgets | |
parent | 84d5482d2886b617181380acb3f7e2fbb34a71b5 (diff) |
add experimental support for long textfields
Diffstat (limited to 'src/ui/widgets')
-rw-r--r-- | src/ui/widgets/mod.rs | 2 | ||||
-rw-r--r-- | src/ui/widgets/tui_menu.rs | 1 | ||||
-rw-r--r-- | src/ui/widgets/tui_tab.rs | 2 | ||||
-rw-r--r-- | src/ui/widgets/tui_text.rs | 168 | ||||
-rw-r--r-- | src/ui/widgets/tui_textfield.rs | 79 |
5 files changed, 200 insertions, 52 deletions
diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index b09b4df..66ad76f 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -4,6 +4,7 @@ mod tui_footer; mod tui_menu; mod tui_prompt; mod tui_tab; +mod tui_text; mod tui_textfield; mod tui_topbar; mod tui_worker; @@ -14,6 +15,7 @@ pub use self::tui_footer::TuiFooter; pub use self::tui_menu::{TuiCommandMenu, TuiMenu}; pub use self::tui_prompt::TuiPrompt; pub use self::tui_tab::TuiTabBar; +pub use self::tui_text::TuiMultilineText; pub use self::tui_textfield::TuiTextField; pub use self::tui_topbar::TuiTopBar; pub use self::tui_worker::TuiWorker; diff --git a/src/ui/widgets/tui_menu.rs b/src/ui/widgets/tui_menu.rs index 0f2570d..16c2cd9 100644 --- a/src/ui/widgets/tui_menu.rs +++ b/src/ui/widgets/tui_menu.rs @@ -53,7 +53,6 @@ impl TuiCommandMenu { .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) diff --git a/src/ui/widgets/tui_tab.rs b/src/ui/widgets/tui_tab.rs index ce16887..3ab0941 100644 --- a/src/ui/widgets/tui_tab.rs +++ b/src/ui/widgets/tui_tab.rs @@ -1,7 +1,7 @@ use tui::buffer::Buffer; use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; -use tui::text::{Span, Spans}; +use tui::text::Span; use tui::widgets::{Paragraph, Widget, Wrap}; use unicode_width::UnicodeWidthStr; diff --git a/src/ui/widgets/tui_text.rs b/src/ui/widgets/tui_text.rs new file mode 100644 index 0000000..bd6d5fa --- /dev/null +++ b/src/ui/widgets/tui_text.rs @@ -0,0 +1,168 @@ +use tui::buffer::Buffer; +use tui::layout::Rect; +use tui::style::{Color, Modifier, Style}; +use tui::text::{Span, Spans}; +use tui::widgets::{Paragraph, Widget, Wrap}; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + +use crate::context::JoshutoContext; +use crate::io::FileOp; +use crate::ui::widgets::TuiTopBar; + +#[derive(Clone, Debug)] +struct IndexInfo { + pub index: usize, + pub x: usize, + pub y: usize, + pub c: char, +} + +#[derive(Clone, Debug)] +struct LineInfo { + pub start: usize, + pub end: usize, + pub width: usize, +} + +pub struct TuiMultilineText<'a> { + _s: &'a str, + _width: usize, + _lines: Vec<LineInfo>, + _style: Style, + _cursor_style: Style, + _index_info: Option<IndexInfo>, +} + +impl<'a> TuiMultilineText<'a> { + pub fn new(s: &'a str, area_width: usize, index: Option<usize>) -> Self { + let filter = |(i, c): (usize, char)| { + let w = c.width()?; + Some((i, c, w)) + }; + + let mut lines = Vec::with_capacity(s.len() / area_width + 1); + let mut start = 0; + let mut end = 0; + let mut line_width = 0; + + for (i, c, w) in s.char_indices().filter_map(filter) { + end = i + c.len_utf8(); + if (line_width + w < area_width) { + line_width += w; + continue; + } + lines.push(LineInfo { + start, + end, + width: line_width, + }); + start = end; + line_width = 0; + } + if (start < end) { + lines.push(LineInfo { + start, + end: end, + width: line_width, + }); + } + + let mut index_info = None; + if let Some(idx) = index { + let (row, line_info) = lines + .iter() + .enumerate() + .find(|(r, li)| li.start <= idx && li.end > idx) + .unwrap(); + + let mut s_width = 0; + let substr = &s[line_info.start..line_info.end]; + for (i, c, w) in substr.char_indices().filter_map(filter) { + if (line_info.start + i <= idx) { + s_width += w; + continue; + } + index_info = Some(IndexInfo { + index: idx, + x: s_width, + y: row, + c: c, + }); + break; + } + + if let None = index_info { + let s_width = substr.width(); + index_info = Some(IndexInfo { + index: idx, + x: s_width % area_width, + y: row + 1, + c: ' ', + }); + } + } + + let default_style = Style::default().fg(Color::Reset).bg(Color::Reset); + let cursor_style = Style::default() + .fg(Color::White) + .add_modifier(Modifier::REVERSED); + Self { + _s: s, + _lines: lines, + _width: area_width, + _style: default_style, + _cursor_style: cursor_style, + _index_info: index_info, + } + } + + pub fn width(&self) -> usize { + self._width + } + + pub fn len(&self) -> usize { + match self._index_info.as_ref() { + Some(index_info) if index_info.y >= self._lines.len() => index_info.y + 1, + _ => self._lines.len(), + } + } + + pub fn style(&mut self, style: Style) { + self._style = style; + } + + pub fn cursor_style(&mut self, style: Style) { + self._cursor_style = style; + } +} + +impl<'a> Widget for TuiMultilineText<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + let area_left = area.left(); + let area_top = area.top(); + for (i, line_info) in self._lines.iter().enumerate().take(self._lines.len() - 1) { + buf.set_string( + area_left, + area_top + i as u16, + &self._s[line_info.start..line_info.end], + self._style, + ); + } + let line_info = &self._lines[self._lines.len() - 1]; + buf.set_string( + area_left, + area_top + (self._lines.len() - 1) as u16, + &self._s[line_info.start..line_info.end], + self._style, + ); + + if let Some(index_info) = self._index_info { + buf.set_string( + area_left + index_info.x as u16, + area_top + index_info.y as u16, + index_info.c.to_string(), + self._cursor_style, + ); + } + } +} diff --git a/src/ui/widgets/tui_textfield.rs b/src/ui/widgets/tui_textfield.rs index 0aed6df..6e24083 100644 --- a/src/ui/widgets/tui_textfield.rs +++ b/src/ui/widgets/tui_textfield.rs @@ -5,11 +5,11 @@ use termion::event::{Event, Key}; use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; use tui::text::{Span, Spans}; -use tui::widgets::{Clear, Paragraph, Wrap}; +use tui::widgets::{Block, Borders, Clear, Paragraph, Wrap}; use crate::context::JoshutoContext; use crate::ui::views::TuiView; -use crate::ui::widgets::TuiMenu; +use crate::ui::widgets::{TuiMenu, TuiMultilineText}; use crate::ui::TuiBackend; use crate::util::event::JoshutoEvent; use crate::util::input; @@ -36,7 +36,7 @@ pub struct TuiTextField<'a> { _prompt: &'a str, _prefix: &'a str, _suffix: &'a str, - _menu_items: Option<Vec<&'a str>>, + _menu_items: Vec<&'a str>, } impl<'a> TuiTextField<'a> { @@ -44,7 +44,7 @@ impl<'a> TuiTextField<'a> { where I: Iterator<Item = &'a str>, { - self._menu_items = Some(items.collect()); + self._menu_items = items.collect(); self } @@ -86,74 +86,53 @@ impl<'a> TuiTextField<'a> { loop { terminal .draw(|frame| { - let f_size: Rect = frame.size(); - if f_size.height == 0 { + 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, f_size); + frame.render_widget(view, area); } - if let Some(items) = self._menu_items.as_ref() { - let menu_widget = TuiMenu::new(items); + 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 + 2 > f_size.height as usize { + let menu_y = if menu_len + 1 > area.height as usize { 0 } else { - (f_size.height as usize - menu_len - 2) as u16 + (area.height as usize - menu_len - 1) as u16 }; let menu_rect = Rect { x: 0, - y: menu_y, - width: f_size.width, + 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 cursor_xpos = line_buffer.pos(); - let prefix = &line_buffer.as_str()[..cursor_xpos]; - - let curr = line_buffer.as_str()[cursor_xpos..].chars().next(); - let (suffix, curr) = match curr { - Some(c) => { - let curr_len = c.len_utf8(); - (&line_buffer.as_str()[(cursor_xpos + curr_len)..], c) - } - None => ("", ' '), - }; - - let cmd_prompt_style = Style::default().fg(Color::LightGreen); - let cursor_style = Style::default().add_modifier(Modifier::REVERSED); - let default_style = Style::default().fg(Color::Reset).bg(Color::Reset); - - let curr_string = curr.to_string(); - - let text = Spans::from(vec![ - Span::styled(self._prompt, cmd_prompt_style), - Span::styled(prefix, default_style), - Span::styled(curr_string, cursor_style), - Span::styled(suffix, default_style), - Span::styled(" ", default_style), - Span::styled(".", Style::default().fg(Color::Black).bg(Color::Reset)), - ]); - - let textfield_rect = Rect { + let multiline_rect = Rect { x: 0, - y: f_size.height - 1, - width: f_size.width, - height: 1, + y: area.height - multiline_height as u16, + width: area.width, + height: multiline_height as u16, }; - frame.render_widget(Clear, textfield_rect); - frame.render_widget( - Paragraph::new(text).wrap(Wrap { trim: true }), - textfield_rect, - ); + frame.render_widget(Clear, multiline_rect); + frame.render_widget(multiline, multiline_rect); }) .unwrap(); @@ -259,7 +238,7 @@ impl<'a> std::default::Default for TuiTextField<'a> { _prompt: "", _prefix: "", _suffix: "", - _menu_items: None, + _menu_items: vec![], } } } |