Split ui.rs into modules based on UI element #200
@ -6,6 +6,7 @@ mod minibuffer;
|
||||
mod overlay;
|
||||
mod reload;
|
||||
mod style;
|
||||
mod widgets;
|
||||
|
||||
use browse::{AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState};
|
||||
use display::UiDisplay;
|
||||
@ -16,162 +17,36 @@ use musichoard::collection::{album::Album, track::Track, Collection};
|
||||
use overlay::{OverlayBuilder, OverlaySize};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{Block, BorderType, Borders, Clear, List, Paragraph, Wrap},
|
||||
widgets::{Paragraph, Wrap},
|
||||
Frame,
|
||||
};
|
||||
use widgets::UiWidget;
|
||||
|
||||
use crate::tui::{
|
||||
app::{AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState},
|
||||
lib::interface::musicbrainz::Match,
|
||||
ui::{reload::ReloadMenu, style::UiStyle},
|
||||
ui::reload::ReloadMenu,
|
||||
};
|
||||
|
||||
pub trait IUi {
|
||||
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
||||
}
|
||||
|
||||
struct Column<'a> {
|
||||
paragraph: Paragraph<'a>,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
pub struct 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) {
|
||||
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) {
|
||||
Self::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_list_widget("Albums", st.list, st.state, st.active, ar.list, 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) {
|
||||
Self::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_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr);
|
||||
UiWidget::render_info_widget("Track info", st.info, st.active, ar.info, fr);
|
||||
}
|
||||
|
||||
fn render_minibuffer(state: &AppPublicState, ar: Rect, fr: &mut Frame) {
|
||||
@ -185,8 +60,8 @@ impl Ui {
|
||||
height: 1,
|
||||
};
|
||||
|
||||
Self::render_info_widget("Minibuffer", Paragraph::new(""), false, ar, fr);
|
||||
Self::render_columns(mb.paragraphs, mb.columns, false, area, fr);
|
||||
UiWidget::render_info_widget("Minibuffer", Paragraph::new(""), false, ar, fr);
|
||||
UiWidget::render_columns(mb.paragraphs, mb.columns, false, area, fr);
|
||||
}
|
||||
|
||||
fn render_browse_frame(
|
||||
@ -239,16 +114,16 @@ impl Ui {
|
||||
let area = OverlayBuilder::default().build(frame.size());
|
||||
|
||||
if selection.category() == Category::Artist {
|
||||
let artist_overlay = ArtistOverlay::new(artists, &selection.widget_state_artist().list);
|
||||
Self::render_overlay_widget("Artist", artist_overlay.properties, area, false, frame);
|
||||
let overlay = ArtistOverlay::new(artists, &selection.widget_state_artist().list);
|
||||
UiWidget::render_overlay_widget("Artist", overlay.properties, area, false, frame);
|
||||
} else {
|
||||
let no_albums: Vec<Album> = vec![];
|
||||
let albums = selection
|
||||
.state_album(artists)
|
||||
.map(|st| st.list)
|
||||
.unwrap_or_else(|| &no_albums);
|
||||
let album_overlay = AlbumOverlay::new(albums, &selection.widget_state_album().list);
|
||||
Self::render_overlay_widget("Album", album_overlay.properties, area, false, frame);
|
||||
let overlay = AlbumOverlay::new(albums, &selection.widget_state_album().list);
|
||||
UiWidget::render_overlay_widget("Album", overlay.properties, area, false, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,7 +136,7 @@ impl Ui {
|
||||
let area = OverlayBuilder::default().build(frame.size());
|
||||
let matching_string = UiDisplay::display_matching_info(matching);
|
||||
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) {
|
||||
@ -272,7 +147,7 @@ impl Ui {
|
||||
|
||||
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) {
|
||||
@ -284,7 +159,7 @@ impl Ui {
|
||||
.alignment(Alignment::Center)
|
||||
.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