mirror of
https://github.com/talwat/lowfi
synced 2025-12-19 13:13:21 +00:00
style: consolidate interface state into struct
This commit is contained in:
parent
0382a9dcbb
commit
89da41c9ff
@ -131,7 +131,7 @@ impl Player {
|
||||
let list = List::load(args.track_list.as_ref()).await?;
|
||||
|
||||
let sink = Arc::new(rodio::Sink::connect_new(mixer));
|
||||
let state = ui::State::initial(Arc::clone(&sink), args.width, list.name.clone());
|
||||
let state = ui::State::initial(Arc::clone(&sink), list.name.clone());
|
||||
|
||||
let volume = PersistentVolume::load().await?;
|
||||
sink.set_volume(volume.float());
|
||||
|
||||
@ -128,17 +128,14 @@ mod interface {
|
||||
download::PROGRESS,
|
||||
player::Current,
|
||||
tracks,
|
||||
ui::{
|
||||
interface::{self, Params},
|
||||
State,
|
||||
},
|
||||
ui::{Interface, State},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn loading() {
|
||||
let sink = Arc::new(rodio::Sink::new().0);
|
||||
let mut state = State::initial(sink, 3, String::from("test"));
|
||||
let menu = interface::menu(&mut state, Params::default());
|
||||
let mut state = State::initial(sink, String::from("test"));
|
||||
let menu = Interface::default().menu(&mut state);
|
||||
|
||||
assert_eq!(menu[0], "loading ");
|
||||
assert_eq!(menu[1], " [ ] 00:00/00:00 ");
|
||||
@ -157,11 +154,11 @@ mod interface {
|
||||
fn volume() {
|
||||
let sink = Arc::new(rodio::Sink::new().0);
|
||||
sink.set_volume(0.5);
|
||||
let mut state = State::initial(sink, 3, String::from("test"));
|
||||
|
||||
let mut state = State::initial(sink, String::from("test"));
|
||||
state.timer = Some(Instant::now());
|
||||
|
||||
let menu = interface::menu(&mut state, Params::default());
|
||||
|
||||
let menu = Interface::default().menu(&mut state);
|
||||
assert_eq!(menu[0], "loading ");
|
||||
assert_eq!(menu[1], " volume: [///// ] 50% ");
|
||||
assert_eq!(
|
||||
@ -179,10 +176,10 @@ mod interface {
|
||||
fn progress() {
|
||||
let sink = Arc::new(rodio::Sink::new().0);
|
||||
PROGRESS.store(50, std::sync::atomic::Ordering::Relaxed);
|
||||
let mut state = State::initial(sink, 3, String::from("test"));
|
||||
let mut state = State::initial(sink, String::from("test"));
|
||||
state.current = Current::Loading(Some(&PROGRESS));
|
||||
|
||||
let menu = interface::menu(&mut state, Params::default());
|
||||
let menu = Interface::default().menu(&mut state);
|
||||
|
||||
assert_eq!(menu[0], format!("loading {} ", "50%".bold()));
|
||||
assert_eq!(menu[1], " [ ] 00:00/00:00 ");
|
||||
@ -207,9 +204,9 @@ mod interface {
|
||||
duration: Some(Duration::from_secs(8)),
|
||||
};
|
||||
|
||||
let mut state = State::initial(sink, 3, String::from("test"));
|
||||
let mut state = State::initial(sink, String::from("test"));
|
||||
state.current = Current::Track(track.clone());
|
||||
let menu = interface::menu(&mut state, Params::default());
|
||||
let menu = Interface::default().menu(&mut state);
|
||||
|
||||
assert_eq!(
|
||||
menu[0],
|
||||
|
||||
16
src/ui.rs
16
src/ui.rs
@ -11,6 +11,7 @@ pub mod environment;
|
||||
pub use environment::Environment;
|
||||
pub mod input;
|
||||
pub mod interface;
|
||||
pub use interface::Interface;
|
||||
|
||||
#[cfg(feature = "mpris")]
|
||||
pub mod mpris;
|
||||
@ -65,9 +66,6 @@ pub struct State {
|
||||
/// The timer, which is used when the user changes volume to briefly display it.
|
||||
pub(crate) timer: Option<Instant>,
|
||||
|
||||
/// The full inner width of the terminal window.
|
||||
pub(crate) width: usize,
|
||||
|
||||
/// The name of the playing tracklist, for MPRIS.
|
||||
#[allow(dead_code)]
|
||||
list: String,
|
||||
@ -75,10 +73,8 @@ pub struct State {
|
||||
|
||||
impl State {
|
||||
/// Creates an initial UI state.
|
||||
pub fn initial(sink: Arc<rodio::Sink>, width: usize, list: String) -> Self {
|
||||
let width = 21 + width.min(32) * 2;
|
||||
pub fn initial(sink: Arc<rodio::Sink>, list: String) -> Self {
|
||||
Self {
|
||||
width,
|
||||
sink,
|
||||
list,
|
||||
current: Current::default(),
|
||||
@ -163,9 +159,7 @@ impl Handle {
|
||||
mut state: State,
|
||||
params: interface::Params,
|
||||
) -> Result<()> {
|
||||
let mut interval = tokio::time::interval(params.delta);
|
||||
let mut window = interface::Window::new(state.width, params.borderless);
|
||||
let mut clock = params.clock.then(|| interface::Clock::new(&mut window));
|
||||
let mut interface = Interface::new(params);
|
||||
|
||||
loop {
|
||||
if let Ok(message) = rx.try_recv() {
|
||||
@ -177,9 +171,7 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
clock.as_mut().map(|x| x.update(&mut window));
|
||||
interface::draw(&mut state, &mut window, params)?;
|
||||
interval.tick().await;
|
||||
interface.draw(&mut state).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use crate::{ui, Args};
|
||||
use crate::{
|
||||
ui::{self, State},
|
||||
Args,
|
||||
};
|
||||
use std::{env, time::Duration};
|
||||
|
||||
pub mod clock;
|
||||
@ -24,6 +27,9 @@ pub struct Params {
|
||||
/// Whether to include the clock on the top bar.
|
||||
pub clock: bool,
|
||||
|
||||
/// The full inner width of the terminal window.
|
||||
pub(crate) width: usize,
|
||||
|
||||
/// The total delta between frames, which takes into account
|
||||
/// the time it takes to actually render each frame.
|
||||
///
|
||||
@ -47,46 +53,87 @@ impl TryFrom<&Args> for Params {
|
||||
delta,
|
||||
enabled: !disabled,
|
||||
clock: args.clock,
|
||||
width: 21 + args.width.min(32) * 2,
|
||||
minimalist: args.minimalist,
|
||||
borderless: args.borderless,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a full "menu" from the [`ui::State`], which can be
|
||||
/// easily put into a window for display.
|
||||
///
|
||||
/// The menu really is just a [`Vec`] of the different components,
|
||||
/// with padding already added.
|
||||
pub(crate) fn menu(state: &mut ui::State, params: Params) -> Vec<String> {
|
||||
let action = components::action(state, state.width);
|
||||
/// All of the state related to the interface itself,
|
||||
/// which is displayed each frame to the standard output.
|
||||
pub struct Interface {
|
||||
/// The interval to wait between frames.
|
||||
interval: tokio::time::Interval,
|
||||
|
||||
let middle = match state.timer {
|
||||
Some(timer) => {
|
||||
let volume = state.sink.volume();
|
||||
let percentage = format!("{}%", (volume * 100.0).round().abs());
|
||||
if timer.elapsed() > Duration::from_secs(1) {
|
||||
state.timer = None;
|
||||
}
|
||||
/// The [`Window`] to render to.
|
||||
window: Window,
|
||||
|
||||
components::audio_bar(state.width - 17, volume, &percentage)
|
||||
}
|
||||
None => components::progress_bar(state, state.width - 16),
|
||||
};
|
||||
/// The visual clock, which is [`None`] if it has
|
||||
/// been disabled by the [`Params`].
|
||||
clock: Option<Clock>,
|
||||
|
||||
let controls = components::controls(state.width);
|
||||
if params.minimalist {
|
||||
vec![action, middle]
|
||||
} else {
|
||||
vec![action, middle, controls]
|
||||
/// The interface parameters that control smaller
|
||||
/// aesthetic features and options.
|
||||
params: Params,
|
||||
}
|
||||
|
||||
impl Default for Interface {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(Params::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// The code for the terminal interface itself.
|
||||
///
|
||||
/// * `minimalist` - All this does is hide the bottom control bar.
|
||||
pub fn draw(state: &mut ui::State, window: &mut Window, params: Params) -> super::Result<()> {
|
||||
let menu = menu(state, params);
|
||||
window.draw(menu, false)?;
|
||||
Ok(())
|
||||
impl Interface {
|
||||
/// Creates a new interface.
|
||||
pub fn new(params: Params) -> Self {
|
||||
let mut window = Window::new(params.width, params.borderless);
|
||||
|
||||
Self {
|
||||
clock: params.clock.then(|| Clock::new(&mut window)),
|
||||
interval: tokio::time::interval(params.delta),
|
||||
window,
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a full "menu" from the [`ui::State`], which can be
|
||||
/// easily put into a window for display.
|
||||
///
|
||||
/// The menu really is just a [`Vec`] of the different components,
|
||||
/// with padding already added.
|
||||
pub(crate) fn menu(&self, state: &mut State) -> Vec<String> {
|
||||
let action = components::action(state, self.params.width);
|
||||
|
||||
let middle = match state.timer {
|
||||
Some(timer) => {
|
||||
let volume = state.sink.volume();
|
||||
let percentage = format!("{}%", (volume * 100.0).round().abs());
|
||||
if timer.elapsed() > Duration::from_secs(1) {
|
||||
state.timer = None;
|
||||
}
|
||||
|
||||
components::audio_bar(self.params.width - 17, volume, &percentage)
|
||||
}
|
||||
None => components::progress_bar(state, self.params.width - 16),
|
||||
};
|
||||
|
||||
let controls = components::controls(self.params.width);
|
||||
if self.params.minimalist {
|
||||
vec![action, middle]
|
||||
} else {
|
||||
vec![action, middle, controls]
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the terminal. This will also wait for the specified
|
||||
/// delta to pass before completing.
|
||||
pub async fn draw(&mut self, state: &mut State) -> super::Result<()> {
|
||||
self.clock.as_mut().map(|x| x.update(&mut self.window));
|
||||
self.window.draw(self.menu(state), false)?;
|
||||
self.interval.tick().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user