tlc: status bar clock + dialog scrollbars on help/jobs/find
Status bar now shows wall clock (HH:MM UTC) and spinner character right-aligned. Native ratatui scrollbars added to Help, Jobs, and Find dialogs when content overflows visible area.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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![
|
||||
|
||||
@@ -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<Message>,
|
||||
/// 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<Span> = 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<Span> = 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user