diff --git a/src/widgets/components/textarea.rs b/src/widgets/components/textarea.rs index fd4c4bd..15bcab8 100644 --- a/src/widgets/components/textarea.rs +++ b/src/widgets/components/textarea.rs @@ -1,11 +1,11 @@ use color_eyre::Result; -use crossterm::event::Event; +use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use rat_cursor::HasScreenCursor; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::StatefulWidget; use ratatui::style::{Color, Stylize}; -use ratatui::text::Text; +use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Block, Borders, Paragraph, Widget}; use tui_input::backend::crossterm::EventHandler; use tui_input::Input; @@ -16,6 +16,8 @@ pub struct TextArea { title: String, style: TextAreaStyle, input_area: Option, + scroll_offset: u16, + auto_scroll: bool, pub active: bool, } @@ -41,14 +43,29 @@ impl StatefulWidget for TextArea { } else if matches!(self.style, TextAreaStyle::SingleLine) { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(10), Constraint::Fill(0)]) + .constraints([Constraint::Max((self.title.len() + 1) as u16), Constraint::Fill(0)]) .split(area); - let text = Text::from(self.title); - let line = Paragraph::new(text); - let input_text = Text::from(input_value).fg(Color::White); - let paragraph = Paragraph::new(input_text).bg(Color::Green); - state.input_area = Some(chunks[1]); - line.render(chunks[0], buf); + let label_text = Text::from(self.title); + let label = Paragraph::new(label_text); + + // let scroll_offset = if self.input.cursor() > chunks[1].width as usize { + // self.input.cursor() - chunks[1].width as usize + // } else { 0 }; + + let input_text = Span::from(input_value.clone()).fg(Color::White); + let input_line = Line::from(input_text); + let paragraph = Paragraph::new(input_line) + .bg(Color::Green) + .scroll((0, self.scroll_offset)); + + if self.input_area.is_none() { + state.input_area = Some(chunks[1]); + } + else if let Some(area) = self.input_area && area != chunks[1] { + state.input_area = Some(chunks[1]); + } + + label.render(chunks[0], buf); paragraph.render(chunks[1], buf); } } @@ -60,26 +77,44 @@ impl HasScreenCursor for TextArea { return None; } let area = self.input_area.unwrap(); - let width = area.width.max(3) - 3; - let scroll = self.input.visual_scroll(width as usize); - let x = (self.input.visual_cursor().max(scroll) - scroll) as u16; + let scroll = self.input.visual_scroll(1); + let x = self.input.visual_cursor().max(scroll) as u16 - self.scroll_offset; Some((area.x + x, area.y)) } } impl TextArea { - pub fn new(title: &str, placeholder_text: &str, style: Option) -> Self { + pub fn new(title: &str, placeholder_text: &str, style: Option, auto_scroll: Option) -> Self { Self { input: Input::new(placeholder_text.to_string()), title: title.to_string(), active: false, style: style.unwrap_or(TextAreaStyle::SingleLine), input_area: None, + auto_scroll: auto_scroll.unwrap_or(true), + scroll_offset: 0 } } pub fn handle_input(&mut self, event: &Event) -> Result<()> { let _ = self.input.handle_event(event); + + if let Event::Key(key) = event && + !matches!(key.kind, KeyEventKind::Release) && + let Some(area) = self.input_area { + + let cursor_pos = self.input.cursor() as u16; + if self.scroll_offset > cursor_pos { + self.scroll_offset = cursor_pos; + } else if cursor_pos >= area.width + self.scroll_offset { + self.scroll_offset = cursor_pos - area.width; + } else if self.auto_scroll && self.scroll_offset > 0 && (key.code.is_delete() || key.code.is_backspace()) { + self.scroll_offset -= 1; + // HACK: with_cursor function requires to be owned so use handle event + let _ = self.input.handle_event(&Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()))); + } + } + Ok(()) } } diff --git a/src/widgets/popups/folder.rs b/src/widgets/popups/folder.rs index fec5a56..035f62e 100644 --- a/src/widgets/popups/folder.rs +++ b/src/widgets/popups/folder.rs @@ -11,7 +11,7 @@ pub struct AddFolderPopup { impl AddFolderPopup { pub fn new() -> Self { - let mut textarea = TextArea::new("Folder Path", "", None); + let mut textarea = TextArea::new("Folder Path", "", None, None); textarea.active = true; Self { textarea } }