mod footer;
mod library;
mod lyrics;
mod nowplaying;
mod overlay;
mod queue;
mod visualizer;
pub(crate) use footer::*;
pub(crate) use library::*;
pub(crate) use lyrics::*;
pub(crate) use nowplaying::*;
pub(crate) use overlay::*;
pub(crate) use queue::*;
pub(crate) use visualizer::*;
use crate::*;
pub(crate) fn render(f: &mut Frame, app: &App, out: &mut FrameOut, repaint: ArtRepaint) {
let theme = app.theme.displayed;
let area = f.area();
f.render_widget(Block::default().style(theme.base()), area);
let area = area.inner(Margin::new(2, 1));
let rows = Layout::vertical([
Constraint::Length(1), Constraint::Length(1), Constraint::Min(6), Constraint::Length(1), Constraint::Length(2), Constraint::Length(1), ])
.split(area);
let mut header: Vec<Span> =
gradient_line("\u{FF2D}\u{FF39}\u{FF38}", &[theme.primary, theme.accent])
.into_iter()
.map(|mut sp| {
sp.style = sp.style.add_modifier(Modifier::BOLD);
sp
})
.collect();
if !app.status.is_empty() {
header.push(Span::styled(format!(" {}", app.status), theme.muted()));
}
f.render_widget(Paragraph::new(Line::from(header)), rows[0]);
f.render_widget(
Paragraph::new(Line::from(view_tabs(app, theme))).alignment(Alignment::Right),
rows[0],
);
let mut total: usize = 3; for (i, v) in RightView::ALL.iter().enumerate() {
if i > 0 {
total += 3;
} total += v.label().chars().count();
}
let mut tx_x = rows[0]
.right()
.saturating_sub(total as u16)
.saturating_add(3);
let mut tabs = Vec::with_capacity(RightView::ALL.len());
for (i, v) in RightView::ALL.iter().enumerate() {
if i > 0 {
tx_x = tx_x.saturating_add(3);
}
let w = v.label().chars().count() as u16;
tabs.push((
*v,
Rect {
x: tx_x,
y: rows[0].y,
width: w,
height: 1,
},
));
tx_x = tx_x.saturating_add(w);
}
out.hits.tabs = tabs;
let right = if app.view.zen {
out.hits.lib = None;
out.hits.scroll = None;
rows[2]
} else {
let body = Layout::horizontal([Constraint::Percentage(30), Constraint::Min(24)])
.spacing(3)
.split(rows[2]);
render_library(f, app, out, theme, body[0]);
body[1]
};
match app.view.mode {
RightView::NowPlaying => render_nowplaying_view(f, app, theme, right, repaint),
RightView::Lyrics => render_lyrics(f, app, theme, right),
RightView::Queue => render_queue_view(f, app, theme, right),
}
render_now_strip(f, app, out, theme, rows[4]);
render_footer(f, app, theme, rows[5]);
if app.view.actions.is_some() {
render_actions_overlay(f, app, theme, area);
}
}
pub(crate) fn view_tabs<'a>(app: &App, theme: Theme) -> Vec<Span<'a>> {
let mut spans = vec![Span::styled("←→ ", theme.muted())];
for (i, v) in RightView::ALL.iter().enumerate() {
if i > 0 {
spans.push(Span::styled(" · ", theme.muted()));
}
let style = if *v == app.view.mode {
Style::default()
.fg(theme.primary.into())
.add_modifier(Modifier::BOLD)
} else {
theme.muted()
};
spans.push(Span::styled(v.label(), style));
}
spans
}
pub(crate) fn scroll_offset(
offset: usize,
selected: usize,
cap: usize,
total: usize,
margin: usize,
) -> usize {
if cap == 0 || total <= cap {
return 0;
}
let margin = margin.min((cap - 1) / 2);
offset
.min(selected.saturating_sub(margin)) .max((selected + margin + 1).saturating_sub(cap)) .min(total - cap)
}
pub(crate) fn wipe_area(f: &mut Frame, area: Rect) {
let buf = f.buffer_mut();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.reset();
cell.set_diff_option(CellDiffOption::AlwaysUpdate);
}
}
}
}
pub(crate) fn hold_area(f: &mut Frame, area: Rect) {
let buf = f.buffer_mut();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_diff_option(CellDiffOption::Skip);
}
}
}
}
pub(crate) fn force_area(f: &mut Frame, area: Rect) {
let buf = f.buffer_mut();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_diff_option(CellDiffOption::AlwaysUpdate);
}
}
}
}