tlc: panel visual polish — rounded borders, scrollbars, alt rows, 3D buttons
Rounded panel borders (BorderType::Rounded), native ratatui scrollbars on file lists, alternating row colors (even rows lightened), file-size inline bar in panel footer (█ proportional to largest file), 3D button effect (▔ top + ▁ bottom rows when focused), and panel switch border flash (cyan info color for 4 frames on Tab).
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
|
||||
use ratatui::widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::filemanager::panel::Panel;
|
||||
@@ -38,6 +38,12 @@ impl FileManager {
|
||||
return;
|
||||
}
|
||||
|
||||
self.status.set_spinner_char(if self.spinner.is_active() {
|
||||
self.spinner.current_char().to_string()
|
||||
} else {
|
||||
String::new()
|
||||
});
|
||||
|
||||
if !self.panels_visible {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
@@ -240,8 +246,13 @@ impl FileManager {
|
||||
let title = format!(" {} ", format_utils::path_short(panel.path()));
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(if active {
|
||||
Style::default().fg(header_fg).bg(panel_bg)
|
||||
if self.panel_switch_anim < 100 {
|
||||
Style::default().fg(self.theme.info).bg(panel_bg).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(header_fg).bg(panel_bg)
|
||||
}
|
||||
} else {
|
||||
Style::default().fg(self.theme.border).bg(panel_bg)
|
||||
})
|
||||
@@ -303,8 +314,11 @@ impl FileManager {
|
||||
.marked_names()
|
||||
.iter()
|
||||
.any(|n| n == &entry.name);
|
||||
let style =
|
||||
let mut style =
|
||||
entry_style(entry, active, idx == cursor, is_marked, &self.theme);
|
||||
if row % 2 == 0 && idx != cursor && !is_marked {
|
||||
style = style.bg(self.theme.alt_row_bg());
|
||||
}
|
||||
let prefix = if is_marked { "*" } else { " " };
|
||||
let display =
|
||||
format!("{prefix}{}", format_line_mode(entry, mode, line_width));
|
||||
@@ -317,16 +331,63 @@ impl FileManager {
|
||||
let list = List::new(items)
|
||||
.style(Style::default().fg(panel_fg).bg(panel_bg));
|
||||
frame.render_widget(list, list_area);
|
||||
|
||||
let total = panel.entry_count().max(1);
|
||||
let mut sb_state = ScrollbarState::new(total)
|
||||
.viewport_content_length(body_height as usize)
|
||||
.position(top);
|
||||
frame.render_stateful_widget(
|
||||
Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.thumb_style(Style::default().fg(self.theme.border)),
|
||||
list_area,
|
||||
&mut sb_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if show_footer {
|
||||
let footer_y = inner.y + inner.height.saturating_sub(1);
|
||||
let footer_text = format_utils::panel_footer_text(panel);
|
||||
let mut footer_spans: Vec<Span> = vec![Span::styled(
|
||||
footer_text,
|
||||
Style::default().fg(disabled_fg).bg(panel_bg),
|
||||
)];
|
||||
if let Some(entry) = panel.entries().get(cursor) {
|
||||
if !entry.is_dir() && entry.name != ".." {
|
||||
let max_size = panel
|
||||
.entries()
|
||||
.iter()
|
||||
.filter(|e| !e.is_dir())
|
||||
.map(|e| e.stat.size)
|
||||
.max()
|
||||
.unwrap_or(1)
|
||||
.max(1);
|
||||
let ratio = (entry.stat.size.min(max_size) * 10 / max_size).min(10) as usize;
|
||||
let bar: String = "\u{2588}".repeat(ratio);
|
||||
let pad = 10usize.saturating_sub(ratio);
|
||||
footer_spans.push(Span::raw(" "));
|
||||
footer_spans.push(Span::styled(
|
||||
bar,
|
||||
Style::default().fg(self.theme.info).bg(panel_bg),
|
||||
));
|
||||
if pad > 0 {
|
||||
footer_spans.push(Span::styled(
|
||||
"\u{2591}".repeat(pad),
|
||||
Style::default().fg(self.theme.hidden).bg(panel_bg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let total_w = footer_spans.iter().map(|s| s.width() as u16).sum::<u16>();
|
||||
if total_w < inner.width {
|
||||
let pad = inner.width - total_w;
|
||||
footer_spans.insert(0, Span::styled(
|
||||
" ".repeat(pad as usize),
|
||||
Style::default().bg(panel_bg),
|
||||
));
|
||||
}
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(
|
||||
format_utils::panel_footer_text(panel),
|
||||
Style::default().fg(disabled_fg).bg(panel_bg),
|
||||
)),
|
||||
Paragraph::new(Line::from(footer_spans)),
|
||||
Rect::new(inner.x, footer_y, inner.width, 1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,23 @@ impl Button {
|
||||
Style::default().fg(pair.fg).bg(pair.bg),
|
||||
));
|
||||
let p = Paragraph::new(Line::from(spans)).style(Style::default().bg(pair.bg).fg(pair.fg));
|
||||
frame.render_widget(p, area);
|
||||
if self.focused && !self.disabled && area.height >= 3 {
|
||||
let top_row = "\u{2594}".repeat(area.width as usize);
|
||||
frame.render_widget(
|
||||
Paragraph::new(top_row)
|
||||
.style(Style::default().fg(theme.button_highlight()).bg(pair.bg)),
|
||||
Rect::new(area.x, area.y, area.width, 1),
|
||||
);
|
||||
frame.render_widget(p, Rect::new(area.x, area.y + 1, area.width, 1));
|
||||
let bot_row = "\u{2581}".repeat(area.width as usize);
|
||||
frame.render_widget(
|
||||
Paragraph::new(bot_row)
|
||||
.style(Style::default().fg(theme.button_shadow()).bg(pair.bg)),
|
||||
Rect::new(area.x, area.y + 2, area.width, 1),
|
||||
);
|
||||
} else {
|
||||
frame.render_widget(p, area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user