Implement cm-dashboard style layout
- Add title bar at top: 'cm-player • Playing/Stopped' (cyan bg) - Three-section vertical layout: title, content, status bar - Content area: left (files) | right (player + playlist) - Bottom status bar centered with • separators - Player state moved to title bar - Progress and volume in Player panel - Matches cm-dashboard layout structure
This commit is contained in:
parent
7f5aa7602d
commit
cc86f8eb55
@ -1,6 +1,6 @@
|
|||||||
use crate::state::{AppState, PlayerState};
|
use crate::state::{AppState, PlayerState};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||||
@ -8,19 +8,26 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn render(frame: &mut Frame, state: &AppState) {
|
pub fn render(frame: &mut Frame, state: &AppState) {
|
||||||
|
// Three-section layout: title bar, main content, statusbar (like cm-dashboard)
|
||||||
let main_chunks = Layout::default()
|
let main_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
.constraints([
|
||||||
|
Constraint::Length(1), // Title bar
|
||||||
|
Constraint::Min(0), // Main content
|
||||||
|
Constraint::Length(1), // Status bar
|
||||||
|
])
|
||||||
.split(frame.area());
|
.split(frame.area());
|
||||||
|
|
||||||
let top_chunks = Layout::default()
|
// Main content: left (files) | right (status + playlist)
|
||||||
|
let content_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.split(main_chunks[0]);
|
.split(main_chunks[1]);
|
||||||
|
|
||||||
render_file_panel(frame, state, top_chunks[0]);
|
render_title_bar(frame, state, main_chunks[0]);
|
||||||
render_right_panel(frame, state, top_chunks[1]);
|
render_file_panel(frame, state, content_chunks[0]);
|
||||||
render_status_bar(frame, state, main_chunks[1]);
|
render_right_panel(frame, state, content_chunks[1]);
|
||||||
|
render_status_bar(frame, state, main_chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_file_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
fn render_file_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
||||||
@ -65,17 +72,7 @@ fn render_right_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
|||||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// Combined status line: State | Progress | Volume
|
// Combined status line: Progress | Volume
|
||||||
let state_text = if state.is_refreshing {
|
|
||||||
"Refreshing library..."
|
|
||||||
} else {
|
|
||||||
match state.player_state {
|
|
||||||
PlayerState::Stopped => "Stopped",
|
|
||||||
PlayerState::Playing => "Playing",
|
|
||||||
PlayerState::Paused => "Paused",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let progress_text = if state.current_duration > 0.0 {
|
let progress_text = if state.current_duration > 0.0 {
|
||||||
let position_mins = (state.current_position / 60.0) as u32;
|
let position_mins = (state.current_position / 60.0) as u32;
|
||||||
let position_secs = (state.current_position % 60.0) as u32;
|
let position_secs = (state.current_position % 60.0) as u32;
|
||||||
@ -89,9 +86,9 @@ fn render_right_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
|||||||
"00:00/00:00".to_string()
|
"00:00/00:00".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let combined_status = format!("{} | {} | Vol: {}%", state_text, progress_text, state.volume);
|
let combined_status = format!("{} • Vol: {}%", progress_text, state.volume);
|
||||||
let status_widget = Paragraph::new(combined_status)
|
let status_widget = Paragraph::new(combined_status)
|
||||||
.block(Block::default().borders(Borders::ALL).title("Status"))
|
.block(Block::default().borders(Borders::ALL).title("Player"))
|
||||||
.style(Style::default().fg(Color::White));
|
.style(Style::default().fg(Color::White));
|
||||||
frame.render_widget(status_widget, chunks[0]);
|
frame.render_widget(status_widget, chunks[0]);
|
||||||
|
|
||||||
@ -133,30 +130,30 @@ fn render_right_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
|||||||
frame.render_widget(playlist_widget, chunks[1]);
|
frame.render_widget(playlist_widget, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_status_bar(frame: &mut Frame, _state: &AppState, area: Rect) {
|
fn render_title_bar(frame: &mut Frame, state: &AppState, area: Rect) {
|
||||||
let help = Line::from(vec![
|
let status_text = if state.is_refreshing {
|
||||||
Span::styled("j/k", Style::default().fg(Color::Cyan)),
|
"Refreshing library..."
|
||||||
Span::raw(" Nav | "),
|
} else {
|
||||||
Span::styled("h/l", Style::default().fg(Color::Cyan)),
|
match state.player_state {
|
||||||
Span::raw(" Fold | "),
|
PlayerState::Stopped => "Stopped",
|
||||||
Span::styled("t", Style::default().fg(Color::Cyan)),
|
PlayerState::Playing => "Playing",
|
||||||
Span::raw(" Mark | "),
|
PlayerState::Paused => "Paused",
|
||||||
Span::styled("c", Style::default().fg(Color::Cyan)),
|
}
|
||||||
Span::raw(" Clear | "),
|
};
|
||||||
Span::styled("Enter", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::raw(" Play | "),
|
|
||||||
Span::styled("Space", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::raw(" Pause | "),
|
|
||||||
Span::styled("n/p", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::raw(" Next/Prev | "),
|
|
||||||
Span::styled("r", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::raw(" Rescan | "),
|
|
||||||
Span::styled("q", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::raw(" Quit"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let status_bar = Paragraph::new(help)
|
let title_text = format!("cm-player • {}", status_text);
|
||||||
.style(Style::default().fg(Color::White).bg(Color::DarkGray));
|
let title = Paragraph::new(title_text)
|
||||||
|
.style(Style::default().fg(Color::Black).bg(Color::Cyan));
|
||||||
|
|
||||||
|
frame.render_widget(title, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_status_bar(frame: &mut Frame, _state: &AppState, area: Rect) {
|
||||||
|
let shortcuts = "↑↓/jk: Navigate • h/l: Fold • t: Mark • c: Clear • Enter: Play • Space: Pause • n/p: Next/Prev • r: Rescan • q: Quit";
|
||||||
|
|
||||||
|
let status_bar = Paragraph::new(shortcuts)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
frame.render_widget(status_bar, area);
|
frame.render_widget(status_bar, area);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user