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