diff --git a/local/recipes/tui/tlc/source/src/filemanager/find.rs b/local/recipes/tui/tlc/source/src/filemanager/find.rs index 6202cc2aa9..f1d1e35f61 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/find.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/find.rs @@ -27,7 +27,7 @@ use std::sync::Arc; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{List, ListItem, Paragraph, Wrap}; +use ratatui::widgets::{List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap}; use ratatui::Frame; use walkdir::WalkDir; @@ -344,6 +344,19 @@ impl FindDialog { let list = List::new(items); frame.render_widget(list, chunks[2]); + if self.results.len() > visible_h { + let pos = self.cursor.min(self.results.len().saturating_sub(visible_h)); + let mut sb_state = ScrollbarState::new(self.results.len()) + .viewport_content_length(visible_h) + .position(pos); + frame.render_stateful_widget( + Scrollbar::new(ScrollbarOrientation::VerticalRight) + .thumb_style(Style::default().fg(theme.border)), + chunks[2], + &mut sb_state, + ); + } + // Hint + status. let status: &str = match self.last_error.as_deref() { Some(e) => e, diff --git a/local/recipes/tui/tlc/source/src/filemanager/help.rs b/local/recipes/tui/tlc/source/src/filemanager/help.rs index d21febf761..f0b23d3c11 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/help.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/help.rs @@ -17,7 +17,7 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{List, ListItem, Paragraph}; +use ratatui::widgets::{List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState}; use ratatui::Frame; use crate::key::Key; @@ -380,6 +380,17 @@ impl HelpDialog { .bg(body_bg), ); frame.render_widget(list, chunks[0]); + + let total = self.bindings.len(); + let mut sb_state = ScrollbarState::new(total) + .viewport_content_length(visible) + .position(offset); + frame.render_stateful_widget( + Scrollbar::new(ScrollbarOrientation::VerticalRight) + .thumb_style(Style::default().fg(self.theme.border)), + chunks[0], + &mut sb_state, + ); } // Hint line. diff --git a/local/recipes/tui/tlc/source/src/filemanager/jobs.rs b/local/recipes/tui/tlc/source/src/filemanager/jobs.rs index ff7d94a972..470acda2e1 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/jobs.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/jobs.rs @@ -42,7 +42,7 @@ use std::thread; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Paragraph, Wrap}; +use ratatui::widgets::{Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap}; use ratatui::Frame; use crate::key::Key; @@ -743,6 +743,18 @@ impl JobsDialog { }) .collect(); frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), chunks[1]); + + if n > visible { + let mut sb_state = ScrollbarState::new(n) + .viewport_content_length(visible) + .position(start); + frame.render_stateful_widget( + Scrollbar::new(ScrollbarOrientation::VerticalRight) + .thumb_style(Style::default().fg(theme.border)), + chunks[1], + &mut sb_state, + ); + } } let hint = Line::from(vec![ diff --git a/local/recipes/tui/tlc/source/src/terminal/status.rs b/local/recipes/tui/tlc/source/src/terminal/status.rs index c9b8e271d8..00f3f9e900 100644 --- a/local/recipes/tui/tlc/source/src/terminal/status.rs +++ b/local/recipes/tui/tlc/source/src/terminal/status.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant}; use ratatui::layout::Rect; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Paragraph, Wrap}; +use ratatui::widgets::Paragraph; use ratatui::Frame; use super::color::Theme; @@ -53,6 +53,8 @@ pub struct StatusLine { message: Option, /// Hint text for the active command (F-key tooltip). hint: String, + /// Spinner character shown at the right edge when jobs are active. + spinner_char: String, } impl StatusLine { @@ -86,35 +88,71 @@ impl StatusLine { self.hint = hint.into(); } + /// Set the spinner character shown at the right edge of the status bar. + pub fn set_spinner_char(&mut self, ch: String) { + self.spinner_char = ch; + } + /// Render the status line into a frame. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { let status_pair = mc_skin::color_pair(theme.name, "statusbar", "_default_"); let status_fg = status_pair.map(|p| p.fg).unwrap_or(theme.status_fg); let status_bg = status_pair.map(|p| p.bg).unwrap_or(theme.status_bg); let now_message = self.message.as_ref().filter(|m| !m.expired()); - let line = match now_message { - Some(m) => Line::from(vec![ - Span::styled( - m.text(), - Style::default() - .fg(status_fg) - .bg(status_bg) - .add_modifier(Modifier::BOLD), - ), - Span::raw(" "), - Span::styled( - &self.hint, - Style::default().fg(status_fg).bg(status_bg), - ), - ]), - None => Line::from(Span::styled( - &self.hint, - Style::default().fg(status_fg).bg(status_bg), - )), - }; - let para = Paragraph::new(line) - .style(Style::default().fg(status_fg).bg(status_bg)) - .wrap(Wrap { trim: false }); + + let secs = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + let clock = format!( + "{:02}:{:02}", + (secs / 3600) % 24, + (secs / 60) % 60 + ); + + let mut left_spans: Vec = Vec::new(); + if let Some(m) = now_message { + left_spans.push(Span::styled( + m.text(), + Style::default() + .fg(status_fg) + .bg(status_bg) + .add_modifier(Modifier::BOLD), + )); + left_spans.push(Span::raw(" ")); + } + left_spans.push(Span::styled( + &self.hint, + Style::default().fg(status_fg).bg(status_bg), + )); + + let mut right_spans: Vec = Vec::new(); + if !self.spinner_char.is_empty() { + right_spans.push(Span::styled( + self.spinner_char.clone(), + Style::default().fg(theme.info).bg(status_bg), + )); + right_spans.push(Span::raw(" ")); + } + right_spans.push(Span::styled( + clock, + Style::default().fg(status_fg).bg(status_bg), + )); + + let left_w: usize = left_spans.iter().map(|s| s.width()).sum(); + let right_w: usize = right_spans.iter().map(|s| s.width()).sum(); + let avail = area.width as usize; + let pad = avail.saturating_sub(left_w + right_w); + + let mut all_spans = left_spans; + all_spans.push(Span::styled( + " ".repeat(pad), + Style::default().bg(status_bg), + )); + all_spans.extend(right_spans); + + let para = Paragraph::new(Line::from(all_spans)) + .style(Style::default().fg(status_fg).bg(status_bg)); frame.render_widget(para, area); } }