summaryrefslogtreecommitdiffstats
path: root/src/ui/widgets
diff options
context:
space:
mode:
authorJeff Zhao <jeff.no.zhao@gmail.com>2021-02-09 16:01:56 -0500
committerJeff Zhao <jeff.no.zhao@gmail.com>2021-02-09 16:01:56 -0500
commit621bd8856f8e8e36ce9ff0351dedeff2a4734def (patch)
tree308335fe9560c7c178b1273be62d6942b33acef6 /src/ui/widgets
parent84d5482d2886b617181380acb3f7e2fbb34a71b5 (diff)
add experimental support for long textfields
Diffstat (limited to 'src/ui/widgets')
-rw-r--r--src/ui/widgets/mod.rs2
-rw-r--r--src/ui/widgets/tui_menu.rs1
-rw-r--r--src/ui/widgets/tui_tab.rs2
-rw-r--r--src/ui/widgets/tui_text.rs168
-rw-r--r--src/ui/widgets/tui_textfield.rs79
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![],
}
}
}