From d3c439342ac16ff4ce0b07600bba6eac7119b63c Mon Sep 17 00:00:00 2001 From: Henrik Friedrichsen Date: Wed, 27 Feb 2019 22:50:31 +0100 Subject: [PATCH] introduce global event sink to trigger updates across threads --- Cargo.toml | 1 + src/events.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 30 +++++++++++++++++++++++++----- src/queue.rs | 26 +++++++++++++++++++++++--- src/ui/queue.rs | 48 +++++++++++++++++++++++++----------------------- 5 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 src/events.rs diff --git a/Cargo.toml b/Cargo.toml index 491e99c..ba11892 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Henrik Friedrichsen "] [dependencies] cursive = "0.10" +crossbeam-channel = "0.3.8" env_logger = "0.5.13" failure = "0.1.3" futures = "0.1" diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 0000000..10718fd --- /dev/null +++ b/src/events.rs @@ -0,0 +1,39 @@ +use crossbeam_channel::{unbounded, Sender, Receiver, TryIter}; +use cursive::{Cursive, CbFunc}; + +pub enum Event { + QueueUpdate, +} + +pub type EventSender = Sender; + +pub struct EventManager { + tx: EventSender, + rx: Receiver, + cursive_sink: Sender>, +} + +impl EventManager { + pub fn new(cursive_sink: Sender>) -> EventManager { + let (tx, rx) = unbounded(); + + EventManager { + tx: tx, + rx: rx, + cursive_sink: cursive_sink, + } + } + + pub fn msg_iter(&self) -> TryIter { + self.rx.try_iter() + } + + pub fn sink(&mut self) -> EventSender { + self.tx.clone() + } + + pub fn trigger(&self) { + // send a no-op to trigger event loop processing + self.cursive_sink.send(Box::new(|_s: &mut Cursive| {})); + } +} diff --git a/src/main.rs b/src/main.rs index a0482d4..7f7f7d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +extern crate crossbeam_channel; extern crate cursive; extern crate failure; extern crate futures; @@ -23,15 +24,19 @@ use std::sync::Arc; use std::sync::Mutex; use cursive::event::Key; +use cursive::view::ScrollStrategy; use cursive::views::*; use cursive::Cursive; mod config; +mod events; mod queue; mod spotify; mod theme; mod ui; +use events::{Event, EventManager}; + fn init_logger(content: TextContent) { let mut builder = env_logger::Builder::from_default_env(); { @@ -65,6 +70,8 @@ fn main() { init_logger(logbuf); let mut cursive = Cursive::default(); + let mut event_manager = EventManager::new(cursive.cb_sink().clone()); + cursive.add_global_callback('q', |s| s.quit()); cursive.set_theme(theme::default()); @@ -77,7 +84,7 @@ fn main() { }; let cfg = config::load(path.to_str().unwrap()).expect("could not load configuration file"); - let queue = Arc::new(Mutex::new(queue::Queue::new())); + let queue = Arc::new(Mutex::new(queue::Queue::new(event_manager.sink()))); let spotify = Arc::new(spotify::Spotify::new( cfg.username, @@ -86,6 +93,10 @@ fn main() { queue.clone(), )); + let track = TextView::new("Track Title"); + let pos = TextView::new("[0:00/0:00]"); + let status = LinearLayout::horizontal().child(track).child(pos); + let searchscreen = cursive.active_screen(); let search = ui::search::SearchView::new(spotify.clone(), queue.clone()); cursive.add_fullscreen_layer(search.view); @@ -103,10 +114,13 @@ fn main() { s.set_screen(logscreen); }); - cursive.add_global_callback(Key::F2, move |s| { - s.set_screen(queuescreen); - queue.redraw(s); - }); + { + let event_sink = event_manager.sink(); + cursive.add_global_callback(Key::F2, move |s| { + s.set_screen(queuescreen); + event_sink.send(Event::QueueUpdate); + }); + } cursive.add_global_callback(Key::F3, move |s| { s.set_screen(searchscreen); @@ -115,5 +129,11 @@ fn main() { // cursive event loop while cursive.is_running() { cursive.step(); + for event in event_manager.msg_iter() { + trace!("event received"); + match event { + Event::QueueUpdate => queue.redraw(&mut cursive) + } + } } } diff --git a/src/queue.rs b/src/queue.rs index c840f56..eea2253 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -3,24 +3,44 @@ use std::collections::VecDeque; use rspotify::spotify::model::track::FullTrack; +use events::{Event, EventSender}; + pub struct Queue { queue: VecDeque, + ev_sink: EventSender, } impl Queue { - pub fn new() -> Queue { + pub fn new(ev_sink: EventSender) -> Queue { Queue { queue: VecDeque::new(), + ev_sink: ev_sink, } } + fn send_event(&self) { + self.ev_sink.send(Event::QueueUpdate); + } pub fn remove(&mut self, index: usize) -> Option { - self.queue.remove(index) + match self.queue.remove(index) { + Some(track) => { + self.send_event(); + Some(track) + }, + None => None + } } pub fn enqueue(&mut self, track: FullTrack) { self.queue.push_back(track); + self.send_event(); } pub fn dequeue(&mut self) -> Option { - self.queue.pop_front() + match self.queue.pop_front() { + Some(track) => { + self.send_event(); + Some(track) + }, + None => None + } } pub fn iter(&self) -> Iter { self.queue.iter() diff --git a/src/ui/queue.rs b/src/ui/queue.rs index ed549ec..b2af0f4 100644 --- a/src/ui/queue.rs +++ b/src/ui/queue.rs @@ -33,32 +33,34 @@ impl QueueView { } pub fn redraw(&self, s: &mut Cursive) { - let mut queuelist: ViewRef = s.find_id("queue_list").unwrap(); - queuelist.clear(); + let view_ref: Option> = s.find_id("queue_list"); + if let Some(mut queuelist) = view_ref { + queuelist.clear(); - let queue_ref = self.queue.clone(); - let queue = self.queue.lock().unwrap(); - for (index, track) in queue.iter().enumerate() { - let artists = track - .artists - .iter() - .map(|ref artist| artist.name.clone()) - .collect::>() - .join(", "); - let formatted = format!("{} - {}", artists, track.name); + let queue_ref = self.queue.clone(); + let queue = self.queue.lock().unwrap(); + for (index, track) in queue.iter().enumerate() { + let artists = track + .artists + .iter() + .map(|ref artist| artist.name.clone()) + .collect::>() + .join(", "); + let formatted = format!("{} - {}", artists, track.name); - let trackid = SpotifyId::from_base62(&track.id).expect("could not load track"); - let s = self.spotify.clone(); + let trackid = SpotifyId::from_base62(&track.id).expect("could not load track"); + let s = self.spotify.clone(); - let queue_ref = queue_ref.clone(); - let button = Button::new_raw(formatted, move |_cursive| { - s.load(trackid); - s.play(); - queue_ref.lock().unwrap().remove(index); - // TODO: update view representation, preferably, queue changes - // cause a view refresh - }); - queuelist.add_child("", button); + let queue_ref = queue_ref.clone(); + let button = Button::new_raw(formatted, move |_cursive| { + s.load(trackid); + s.play(); + queue_ref.lock().unwrap().remove(index); + // TODO: update view representation, preferably, queue changes + // cause a view refresh + }); + queuelist.add_child("", button); + } } } }