Reworked statistics

This commit is contained in:
Ultradesu
2026-07-26 01:41:16 +03:00
parent 17eb6a4fee
commit ada18a4583
14 changed files with 957 additions and 216 deletions
+42 -39
View File
@@ -42,7 +42,7 @@ pub fn draw(frame: &mut Frame, state: &AppState, keymap: &Keymap) {
if state.visualizer.active {
crate::visualizer::draw(frame, state);
if state.shutting_down {
draw_shutdown(frame);
draw_shutdown(frame, state);
}
return;
}
@@ -65,22 +65,22 @@ pub fn draw(frame: &mut Frame, state: &AppState, keymap: &Keymap) {
draw_status(frame, status_area, state);
if state.help_visible {
draw_help(frame, keymap);
draw_help(frame, keymap, state);
}
popup::draw(frame, state);
if state.shutting_down {
draw_shutdown(frame);
draw_shutdown(frame, state);
}
}
fn draw_shutdown(frame: &mut Frame) {
fn draw_shutdown(frame: &mut Frame, state: &AppState) {
let area = centered(frame.area(), 28, 5);
frame.render_widget(Clear, area);
let block = Block::bordered().border_style(theme::accent());
let block = Block::bordered().border_style(theme::strong_border_for(state));
let inner = block.inner(area);
frame.render_widget(block, area);
frame.render_widget(
Paragraph::new(Line::styled("Shutting down...", theme::header()))
Paragraph::new(Line::styled("Shutting down...", theme::header_for(state)))
.alignment(Alignment::Center),
Rect {
y: inner.y + inner.height / 2,
@@ -103,7 +103,7 @@ fn centered(area: Rect, width: u16, height: u16) -> Rect {
pub(crate) fn loading_line(state: &AppState, text: impl Into<String>) -> Line<'static> {
Line::from(vec![
Span::styled(format!("{} ", state.spinner()), theme::accent()),
Span::styled(format!("{} ", state.spinner()), theme::accent_for(state)),
Span::styled(text.into(), theme::dim()),
])
}
@@ -128,7 +128,7 @@ fn draw_tabs(frame: &mut Frame, area: Rect, state: &AppState) {
let tabs = Tabs::new(titles)
.select(state.active_tab.index())
.style(theme::dim())
.highlight_style(theme::tab_active())
.highlight_style(theme::tab_active_for(state))
.divider("");
frame.render_widget(tabs, area);
}
@@ -169,7 +169,7 @@ pub(crate) fn track_row_with_like_marker(
let heart = if !show_like_marker {
Span::raw("")
} else if state.track_liked(track) {
Span::styled("", theme::accent())
Span::styled("", theme::accent_for(state))
} else {
Span::raw(" ")
};
@@ -198,10 +198,14 @@ pub(crate) fn track_row_with_like_marker(
area,
);
if visual_selected {
frame.buffer_mut().set_style(area, theme::selection());
frame
.buffer_mut()
.set_style(area, theme::selection_for(state));
}
if selected {
frame.buffer_mut().set_style(area, theme::tab_active());
frame
.buffer_mut()
.set_style(area, theme::tab_active_for(state));
}
}
@@ -246,8 +250,8 @@ fn draw_queue(frame: &mut Frame, area: Rect, state: &AppState) {
" Queue — {} tracks · enter: play · d: remove · shift-v: select · :clear ",
player.queue.len()
))
.title_style(theme::header())
.border_style(theme::dim());
.title_style(theme::header_for(state))
.border_style(theme::border_for(state));
let inner = block.inner(area);
frame.render_widget(block, area);
@@ -329,7 +333,7 @@ fn player_right_line(state: &AppState, width: u16) -> Line<'static> {
if bar_width > 0 && track.duration_seconds > 0.0 {
let ratio = (player.position_secs / track.duration_seconds).clamp(0.0, 1.0);
let filled = (ratio * bar_width as f64).round() as usize;
spans.push(Span::styled("".repeat(filled), theme::accent()));
spans.push(Span::styled("".repeat(filled), theme::accent_for(state)));
spans.push(Span::styled("".repeat(bar_width - filled), theme::dim()));
spans.push(Span::raw(" "));
} else {
@@ -347,14 +351,14 @@ fn player_right_line(state: &AppState, width: u16) -> Line<'static> {
let volume_cells = usize::from(player.volume / 10);
spans.extend([
Span::styled(" vol ", theme::dim()),
Span::styled("".repeat(volume_cells), theme::accent()),
Span::styled("".repeat(volume_cells), theme::accent_for(state)),
Span::styled("".repeat(10 - volume_cells), theme::dim()),
Span::raw(format!(" {:3}%", player.volume)),
Span::raw(" "),
]);
// Enabled modes light up as filled chips; disabled stay dim text.
if player.shuffle {
spans.push(Span::styled(" shuffle ", theme::tab_active()));
spans.push(Span::styled(" shuffle ", theme::tab_active_for(state)));
} else {
spans.push(Span::styled("shuffle off", theme::dim()));
}
@@ -364,21 +368,17 @@ fn player_right_line(state: &AppState, width: u16) -> Line<'static> {
} else {
spans.push(Span::styled(
format!(" repeat {} ", player.repeat.label()),
theme::tab_active(),
theme::tab_active_for(state),
));
}
} else {
spans.push(Span::styled(format!(" {}%", player.volume), theme::dim()));
}
if width >= 70 {
let role_style = match state.device_playback.role {
crate::app::state::DevicePlaybackRole::Active => Style::new().fg(Color::Green),
crate::app::state::DevicePlaybackRole::Control => Style::new().fg(Color::Yellow),
};
spans.push(Span::raw(" "));
spans.push(Span::styled(
state.device_playback.role.label().to_string(),
role_style,
format!(" {} ", state.device_playback.role.label()),
theme::role_pill(state.device_playback.role),
));
spans.push(Span::styled(
format!(" · online {}", state.device_playback.online_devices.max(1)),
@@ -410,10 +410,10 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
if player.paused {
spans.push(Span::styled("", theme::dim()));
} else {
spans.push(Span::styled("", theme::accent()));
spans.push(Span::styled("", theme::accent_for(state)));
}
if state.track_liked(track) {
spans.push(Span::styled("", theme::accent()));
spans.push(Span::styled("", theme::accent_for(state)));
}
spans.push(Span::raw(track.title.clone()));
spans.push(Span::styled(
@@ -430,7 +430,7 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
if state.cmdline.active {
// Vim-style command line takes over the message row.
let mut spans = vec![Span::styled(":", theme::header())];
let mut spans = vec![Span::styled(":", theme::header_for(state))];
spans.extend(line_edit_spans(
&state.cmdline.input,
usize::from(message_row.width.saturating_sub(2)),
@@ -442,10 +442,10 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
let message = match &state.status_message {
Some(message) if is_waiting_message(message) => Line::from(vec![
Span::styled(format!("{} ", state.spinner()), theme::accent()),
Span::styled(message.clone(), theme::accent()),
Span::styled(format!("{} ", state.spinner()), theme::accent_for(state)),
Span::styled(message.clone(), theme::accent_for(state)),
]),
Some(message) => Line::styled(message.clone(), theme::accent()),
Some(message) => Line::styled(message.clone(), theme::accent_for(state)),
None => match active_artist_peer_search(state) {
Some(message) => loading_line(state, message),
None => match &state.player.current {
@@ -461,8 +461,11 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
draw_version(frame, message_row);
if let Some(pending) = &state.pending_keys {
let pending = Paragraph::new(Line::styled(format!("{pending}"), theme::header()))
.alignment(Alignment::Right);
let pending = Paragraph::new(Line::styled(
format!("{pending}"),
theme::header_for(state),
))
.alignment(Alignment::Right);
frame.render_widget(pending, message_row);
}
}
@@ -494,7 +497,7 @@ fn draw_version(frame: &mut Frame, area: Rect) {
/// Help window: bindings merged per action (j / down on one row), grouped
/// into titled sections and laid out in two balanced columns.
fn draw_help(frame: &mut Frame, keymap: &Keymap) {
fn draw_help(frame: &mut Frame, keymap: &Keymap, state: &AppState) {
use crate::app::action::{Action, Category};
use crate::config::keymap::KeyContext;
@@ -528,7 +531,7 @@ fn draw_help(frame: &mut Frame, keymap: &Keymap) {
if rows.is_empty() {
continue;
}
let mut lines = vec![Line::styled(category.title(), theme::header())];
let mut lines = vec![Line::styled(category.title(), theme::header_for(state))];
for row in rows {
let keys = row.keys.join(" / ");
let context = if row.context == KeyContext::Global {
@@ -538,19 +541,19 @@ fn draw_help(frame: &mut Frame, keymap: &Keymap) {
};
let command = row.action.command_hint().unwrap_or("");
lines.push(Line::from(vec![
Span::styled(format!("{keys:<13}"), theme::accent()),
Span::styled(format!("{keys:<13}"), theme::accent_for(state)),
Span::raw(format!(
"{:<24}",
format!("{}{context}", row.action.describe())
)),
Span::styled(command.to_string(), theme::accent()),
Span::styled(command.to_string(), theme::accent_for(state)),
]));
}
lines.push(Line::default());
blocks.push(lines);
}
blocks.push(vec![
Line::styled("Status icons", theme::header()),
Line::styled("Status icons", theme::header_for(state)),
Line::from(vec![
Span::styled("", Style::new().fg(Color::Green)),
Span::raw(" Local on this device"),
@@ -560,7 +563,7 @@ fn draw_help(frame: &mut Frame, keymap: &Keymap) {
Span::raw(" Local + peer sources"),
]),
Line::from(vec![
Span::styled("", theme::accent()),
Span::styled("", theme::accent_for(state)),
Span::raw(" Network only"),
]),
Line::default(),
@@ -585,8 +588,8 @@ fn draw_help(frame: &mut Frame, keymap: &Keymap) {
let block = Block::bordered()
.title(" Keybindings & commands ")
.title_style(theme::header())
.border_style(theme::accent());
.title_style(theme::header_for(state))
.border_style(theme::strong_border_for(state));
let inner = block.inner(area);
frame.render_widget(Clear, area);
frame.render_widget(block, area);