Add scroll to textarea

This commit is contained in:
2025-10-16 14:36:24 +08:00
parent d033d8b93f
commit 5f0238d3e3
2 changed files with 49 additions and 14 deletions

View File

@@ -1,11 +1,11 @@
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::Event; use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use rat_cursor::HasScreenCursor; use rat_cursor::HasScreenCursor;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::StatefulWidget; use ratatui::prelude::StatefulWidget;
use ratatui::style::{Color, Stylize}; use ratatui::style::{Color, Stylize};
use ratatui::text::Text; use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, Borders, Paragraph, Widget}; use ratatui::widgets::{Block, Borders, Paragraph, Widget};
use tui_input::backend::crossterm::EventHandler; use tui_input::backend::crossterm::EventHandler;
use tui_input::Input; use tui_input::Input;
@@ -16,6 +16,8 @@ pub struct TextArea {
title: String, title: String,
style: TextAreaStyle, style: TextAreaStyle,
input_area: Option<Rect>, input_area: Option<Rect>,
scroll_offset: u16,
auto_scroll: bool,
pub active: bool, pub active: bool,
} }
@@ -41,14 +43,29 @@ impl StatefulWidget for TextArea {
} else if matches!(self.style, TextAreaStyle::SingleLine) { } else if matches!(self.style, TextAreaStyle::SingleLine) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(10), Constraint::Fill(0)]) .constraints([Constraint::Max((self.title.len() + 1) as u16), Constraint::Fill(0)])
.split(area); .split(area);
let text = Text::from(self.title); let label_text = Text::from(self.title);
let line = Paragraph::new(text); let label = Paragraph::new(label_text);
let input_text = Text::from(input_value).fg(Color::White);
let paragraph = Paragraph::new(input_text).bg(Color::Green); // let scroll_offset = if self.input.cursor() > chunks[1].width as usize {
state.input_area = Some(chunks[1]); // self.input.cursor() - chunks[1].width as usize
line.render(chunks[0], buf); // } 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); paragraph.render(chunks[1], buf);
} }
} }
@@ -60,26 +77,44 @@ impl HasScreenCursor for TextArea {
return None; return None;
} }
let area = self.input_area.unwrap(); let area = self.input_area.unwrap();
let width = area.width.max(3) - 3; let scroll = self.input.visual_scroll(1);
let scroll = self.input.visual_scroll(width as usize); let x = self.input.visual_cursor().max(scroll) as u16 - self.scroll_offset;
let x = (self.input.visual_cursor().max(scroll) - scroll) as u16;
Some((area.x + x, area.y)) Some((area.x + x, area.y))
} }
} }
impl TextArea { impl TextArea {
pub fn new(title: &str, placeholder_text: &str, style: Option<TextAreaStyle>) -> Self { pub fn new(title: &str, placeholder_text: &str, style: Option<TextAreaStyle>, auto_scroll: Option<bool>) -> Self {
Self { Self {
input: Input::new(placeholder_text.to_string()), input: Input::new(placeholder_text.to_string()),
title: title.to_string(), title: title.to_string(),
active: false, active: false,
style: style.unwrap_or(TextAreaStyle::SingleLine), style: style.unwrap_or(TextAreaStyle::SingleLine),
input_area: None, input_area: None,
auto_scroll: auto_scroll.unwrap_or(true),
scroll_offset: 0
} }
} }
pub fn handle_input(&mut self, event: &Event) -> Result<()> { pub fn handle_input(&mut self, event: &Event) -> Result<()> {
let _ = self.input.handle_event(event); 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(()) Ok(())
} }
} }

View File

@@ -11,7 +11,7 @@ pub struct AddFolderPopup {
impl AddFolderPopup { impl AddFolderPopup {
pub fn new() -> Self { pub fn new() -> Self {
let mut textarea = TextArea::new("Folder Path", "", None); let mut textarea = TextArea::new("Folder Path", "", None, None);
textarea.active = true; textarea.active = true;
Self { textarea } Self { textarea }
} }