Extract widgets
This commit is contained in:
parent
4a22c29eb8
commit
284556f756
@ -6,6 +6,7 @@ mod minibuffer;
|
|||||||
mod overlay;
|
mod overlay;
|
||||||
mod reload;
|
mod reload;
|
||||||
mod style;
|
mod style;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
use browse::{AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState};
|
use browse::{AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState};
|
||||||
use display::UiDisplay;
|
use display::UiDisplay;
|
||||||
@ -16,162 +17,36 @@ use musichoard::collection::{album::Album, track::Track, Collection};
|
|||||||
use overlay::{OverlayBuilder, OverlaySize};
|
use overlay::{OverlayBuilder, OverlaySize};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
widgets::{Block, BorderType, Borders, Clear, List, Paragraph, Wrap},
|
widgets::{Paragraph, Wrap},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use widgets::UiWidget;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState},
|
app::{AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState},
|
||||||
lib::interface::musicbrainz::Match,
|
lib::interface::musicbrainz::Match,
|
||||||
ui::{reload::ReloadMenu, style::UiStyle},
|
ui::reload::ReloadMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IUi {
|
pub trait IUi {
|
||||||
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Column<'a> {
|
|
||||||
paragraph: Paragraph<'a>,
|
|
||||||
area: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ui;
|
pub struct Ui;
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
fn block<'a>(active: bool, error: bool) -> Block<'a> {
|
|
||||||
Block::default().style(UiStyle::block_style(active, error))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_with_borders<'a>(title: &str, active: bool, error: bool) -> Block<'a> {
|
|
||||||
Self::block(active, error)
|
|
||||||
.title_alignment(Alignment::Center)
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.border_type(BorderType::Rounded)
|
|
||||||
.title(format!(" {title} "))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_list_widget(
|
|
||||||
title: &str,
|
|
||||||
list: List,
|
|
||||||
state: &mut WidgetState,
|
|
||||||
active: bool,
|
|
||||||
area: Rect,
|
|
||||||
frame: &mut Frame,
|
|
||||||
) {
|
|
||||||
frame.render_stateful_widget(
|
|
||||||
list.highlight_style(UiStyle::highlight_style(active))
|
|
||||||
.highlight_symbol(">> ")
|
|
||||||
.style(UiStyle::style(active, false))
|
|
||||||
.block(Self::block_with_borders(title, active, false)),
|
|
||||||
area,
|
|
||||||
&mut state.list,
|
|
||||||
);
|
|
||||||
state.height = area.height.saturating_sub(2) as usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_overlay_list_widget(
|
|
||||||
title: &str,
|
|
||||||
list: List,
|
|
||||||
state: &mut WidgetState,
|
|
||||||
active: bool,
|
|
||||||
area: Rect,
|
|
||||||
frame: &mut Frame,
|
|
||||||
) {
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
Self::render_list_widget(title, list, state, active, area, frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_info_widget(
|
|
||||||
title: &str,
|
|
||||||
paragraph: Paragraph,
|
|
||||||
active: bool,
|
|
||||||
area: Rect,
|
|
||||||
frame: &mut Frame,
|
|
||||||
) {
|
|
||||||
frame.render_widget(
|
|
||||||
paragraph
|
|
||||||
.style(UiStyle::style(active, false))
|
|
||||||
.block(Self::block_with_borders(title, active, false)),
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_overlay_widget(
|
|
||||||
title: &str,
|
|
||||||
paragraph: Paragraph,
|
|
||||||
area: Rect,
|
|
||||||
error: bool,
|
|
||||||
frame: &mut Frame,
|
|
||||||
) {
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
frame.render_widget(
|
|
||||||
paragraph
|
|
||||||
.style(UiStyle::style(true, error))
|
|
||||||
.block(Self::block_with_borders(title, true, error)),
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn columns(paragraphs: Vec<Paragraph>, min: u16, area: Rect) -> Vec<Column> {
|
|
||||||
let mut x = area.x;
|
|
||||||
let mut width = area.width;
|
|
||||||
let mut remaining = paragraphs.len() as u16;
|
|
||||||
if remaining < min {
|
|
||||||
remaining = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut blocks = vec![];
|
|
||||||
for paragraph in paragraphs.into_iter() {
|
|
||||||
let block_width = width / remaining;
|
|
||||||
|
|
||||||
blocks.push(Column {
|
|
||||||
paragraph,
|
|
||||||
area: Rect {
|
|
||||||
x,
|
|
||||||
y: area.y,
|
|
||||||
width: block_width,
|
|
||||||
height: area.height,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
x = x.saturating_add(block_width);
|
|
||||||
width = width.saturating_sub(block_width);
|
|
||||||
remaining -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_columns(
|
|
||||||
paragraphs: Vec<Paragraph>,
|
|
||||||
min: u16,
|
|
||||||
active: bool,
|
|
||||||
area: Rect,
|
|
||||||
frame: &mut Frame,
|
|
||||||
) {
|
|
||||||
for column in Self::columns(paragraphs, min, area).into_iter() {
|
|
||||||
frame.render_widget(
|
|
||||||
column
|
|
||||||
.paragraph
|
|
||||||
.style(UiStyle::style(active, false))
|
|
||||||
.block(Self::block(active, false)),
|
|
||||||
column.area,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_artist_column(st: ArtistState, ar: ArtistArea, fr: &mut Frame) {
|
fn render_artist_column(st: ArtistState, ar: ArtistArea, fr: &mut Frame) {
|
||||||
Self::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr);
|
UiWidget::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_album_column(st: AlbumState, ar: AlbumArea, fr: &mut Frame) {
|
fn render_album_column(st: AlbumState, ar: AlbumArea, fr: &mut Frame) {
|
||||||
Self::render_list_widget("Albums", st.list, st.state, st.active, ar.list, fr);
|
UiWidget::render_list_widget("Albums", st.list, st.state, st.active, ar.list, fr);
|
||||||
Self::render_info_widget("Album info", st.info, st.active, ar.info, fr);
|
UiWidget::render_info_widget("Album info", st.info, st.active, ar.info, fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_track_column(st: TrackState, ar: TrackArea, fr: &mut Frame) {
|
fn render_track_column(st: TrackState, ar: TrackArea, fr: &mut Frame) {
|
||||||
Self::render_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr);
|
UiWidget::render_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr);
|
||||||
Self::render_info_widget("Track info", st.info, st.active, ar.info, fr);
|
UiWidget::render_info_widget("Track info", st.info, st.active, ar.info, fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_minibuffer(state: &AppPublicState, ar: Rect, fr: &mut Frame) {
|
fn render_minibuffer(state: &AppPublicState, ar: Rect, fr: &mut Frame) {
|
||||||
@ -185,8 +60,8 @@ impl Ui {
|
|||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::render_info_widget("Minibuffer", Paragraph::new(""), false, ar, fr);
|
UiWidget::render_info_widget("Minibuffer", Paragraph::new(""), false, ar, fr);
|
||||||
Self::render_columns(mb.paragraphs, mb.columns, false, area, fr);
|
UiWidget::render_columns(mb.paragraphs, mb.columns, false, area, fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_browse_frame(
|
fn render_browse_frame(
|
||||||
@ -239,16 +114,16 @@ impl Ui {
|
|||||||
let area = OverlayBuilder::default().build(frame.size());
|
let area = OverlayBuilder::default().build(frame.size());
|
||||||
|
|
||||||
if selection.category() == Category::Artist {
|
if selection.category() == Category::Artist {
|
||||||
let artist_overlay = ArtistOverlay::new(artists, &selection.widget_state_artist().list);
|
let overlay = ArtistOverlay::new(artists, &selection.widget_state_artist().list);
|
||||||
Self::render_overlay_widget("Artist", artist_overlay.properties, area, false, frame);
|
UiWidget::render_overlay_widget("Artist", overlay.properties, area, false, frame);
|
||||||
} else {
|
} else {
|
||||||
let no_albums: Vec<Album> = vec![];
|
let no_albums: Vec<Album> = vec![];
|
||||||
let albums = selection
|
let albums = selection
|
||||||
.state_album(artists)
|
.state_album(artists)
|
||||||
.map(|st| st.list)
|
.map(|st| st.list)
|
||||||
.unwrap_or_else(|| &no_albums);
|
.unwrap_or_else(|| &no_albums);
|
||||||
let album_overlay = AlbumOverlay::new(albums, &selection.widget_state_album().list);
|
let overlay = AlbumOverlay::new(albums, &selection.widget_state_album().list);
|
||||||
Self::render_overlay_widget("Album", album_overlay.properties, area, false, frame);
|
UiWidget::render_overlay_widget("Album", overlay.properties, area, false, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +136,7 @@ impl Ui {
|
|||||||
let area = OverlayBuilder::default().build(frame.size());
|
let area = OverlayBuilder::default().build(frame.size());
|
||||||
let matching_string = UiDisplay::display_matching_info(matching);
|
let matching_string = UiDisplay::display_matching_info(matching);
|
||||||
let st = AlbumMatchesState::new(matches.unwrap_or_default(), state);
|
let st = AlbumMatchesState::new(matches.unwrap_or_default(), state);
|
||||||
Self::render_overlay_list_widget(&matching_string, st.list, st.state, true, area, frame)
|
UiWidget::render_overlay_list_widget(&matching_string, st.list, st.state, true, area, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_reload_overlay(frame: &mut Frame) {
|
fn render_reload_overlay(frame: &mut Frame) {
|
||||||
@ -272,7 +147,7 @@ impl Ui {
|
|||||||
|
|
||||||
let reload_text = ReloadMenu::paragraph().alignment(Alignment::Center);
|
let reload_text = ReloadMenu::paragraph().alignment(Alignment::Center);
|
||||||
|
|
||||||
Self::render_overlay_widget("Reload", reload_text, area, false, frame);
|
UiWidget::render_overlay_widget("Reload", reload_text, area, false, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_error_overlay<S: AsRef<str>>(title: S, msg: S, frame: &mut Frame) {
|
fn render_error_overlay<S: AsRef<str>>(title: S, msg: S, frame: &mut Frame) {
|
||||||
@ -284,7 +159,7 @@ impl Ui {
|
|||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
|
|
||||||
Self::render_overlay_widget(title.as_ref(), error_text, area, true, frame);
|
UiWidget::render_overlay_widget(title.as_ref(), error_text, area, true, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
138
src/tui/ui/widgets.rs
Normal file
138
src/tui/ui/widgets.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Rect},
|
||||||
|
widgets::{Block, BorderType, Borders, Clear, List, Paragraph},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::tui::{app::WidgetState, ui::style::UiStyle};
|
||||||
|
|
||||||
|
struct Column<'a> {
|
||||||
|
paragraph: Paragraph<'a>,
|
||||||
|
area: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UiWidget;
|
||||||
|
|
||||||
|
impl UiWidget {
|
||||||
|
fn block<'a>(active: bool, error: bool) -> Block<'a> {
|
||||||
|
Block::default().style(UiStyle::block_style(active, error))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_with_borders<'a>(title: &str, active: bool, error: bool) -> Block<'a> {
|
||||||
|
Self::block(active, error)
|
||||||
|
.title_alignment(Alignment::Center)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.title(format!(" {title} "))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_list_widget(
|
||||||
|
title: &str,
|
||||||
|
list: List,
|
||||||
|
state: &mut WidgetState,
|
||||||
|
active: bool,
|
||||||
|
area: Rect,
|
||||||
|
frame: &mut Frame,
|
||||||
|
) {
|
||||||
|
frame.render_stateful_widget(
|
||||||
|
list.highlight_style(UiStyle::highlight_style(active))
|
||||||
|
.highlight_symbol(">> ")
|
||||||
|
.style(UiStyle::style(active, false))
|
||||||
|
.block(Self::block_with_borders(title, active, false)),
|
||||||
|
area,
|
||||||
|
&mut state.list,
|
||||||
|
);
|
||||||
|
state.height = area.height.saturating_sub(2) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_overlay_list_widget(
|
||||||
|
title: &str,
|
||||||
|
list: List,
|
||||||
|
state: &mut WidgetState,
|
||||||
|
active: bool,
|
||||||
|
area: Rect,
|
||||||
|
frame: &mut Frame,
|
||||||
|
) {
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
Self::render_list_widget(title, list, state, active, area, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_info_widget(
|
||||||
|
title: &str,
|
||||||
|
paragraph: Paragraph,
|
||||||
|
active: bool,
|
||||||
|
area: Rect,
|
||||||
|
frame: &mut Frame,
|
||||||
|
) {
|
||||||
|
frame.render_widget(
|
||||||
|
paragraph
|
||||||
|
.style(UiStyle::style(active, false))
|
||||||
|
.block(Self::block_with_borders(title, active, false)),
|
||||||
|
area,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_overlay_widget(
|
||||||
|
title: &str,
|
||||||
|
paragraph: Paragraph,
|
||||||
|
area: Rect,
|
||||||
|
error: bool,
|
||||||
|
frame: &mut Frame,
|
||||||
|
) {
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
frame.render_widget(
|
||||||
|
paragraph
|
||||||
|
.style(UiStyle::style(true, error))
|
||||||
|
.block(Self::block_with_borders(title, true, error)),
|
||||||
|
area,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn columns(paragraphs: Vec<Paragraph>, min: u16, area: Rect) -> Vec<Column> {
|
||||||
|
let mut x = area.x;
|
||||||
|
let mut width = area.width;
|
||||||
|
let mut remaining = paragraphs.len() as u16;
|
||||||
|
if remaining < min {
|
||||||
|
remaining = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut blocks = vec![];
|
||||||
|
for paragraph in paragraphs.into_iter() {
|
||||||
|
let block_width = width / remaining;
|
||||||
|
|
||||||
|
blocks.push(Column {
|
||||||
|
paragraph,
|
||||||
|
area: Rect {
|
||||||
|
x,
|
||||||
|
y: area.y,
|
||||||
|
width: block_width,
|
||||||
|
height: area.height,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
x = x.saturating_add(block_width);
|
||||||
|
width = width.saturating_sub(block_width);
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_columns(
|
||||||
|
paragraphs: Vec<Paragraph>,
|
||||||
|
min: u16,
|
||||||
|
active: bool,
|
||||||
|
area: Rect,
|
||||||
|
frame: &mut Frame,
|
||||||
|
) {
|
||||||
|
for column in Self::columns(paragraphs, min, area).into_iter() {
|
||||||
|
frame.render_widget(
|
||||||
|
column
|
||||||
|
.paragraph
|
||||||
|
.style(UiStyle::style(active, false))
|
||||||
|
.block(Self::block(active, false)),
|
||||||
|
column.area,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user