cursive_extras/views/
loading.rs

1use crate::SpannedStrExt;
2use std::{
3    sync::{Arc, RwLock},
4    thread::{self, JoinHandle}
5};
6use crossbeam_channel::{bounded, Receiver, TryRecvError};
7use cursive_core::{
8    Printer, Vec2,
9    view::View,
10    theme::{ColorStyle, PaletteColor},
11    utils::markup::StyledString
12};
13
14type WorkerThread<T> = Arc<RwLock<Option<JoinHandle<T>>>>;
15
16/// Loading animation view that runs a task in a background thread and shows a loading animation and message while it completes and allows for retrieval of a return value.
17/// It is recommended to use `load_resource()` instead of this directly since it has a simpler interface
18/// 
19/// # Example
20/// ```
21/// let mut root = cursive::default();
22/// let anim = LoadingAnimation::new("Loading Dummy Resource...", || {
23///     thread::sleep(Duration::from_secs(10));
24/// });
25/// 
26/// root.add_layer(Dialog::around(anim.with_name("loader")).title("Loading..."));
27/// root.set_fps(30);
28///
29/// root.set_global_callback(Event::Refresh, |root| {
30///     let mut loader = root.find_name::<LoadingAnimation<()>>("loader").unwrap();
31///     if loader.is_done() {
32///         loader.finish().unwrap_or(());
33///         root.quit()
34///     }
35/// });
36/// root.run();
37/// ```
38#[derive(Clone)]
39pub struct LoadingAnimation<T: Send + Sync + 'static> {
40    worker: WorkerThread<T>,
41    recv: Receiver<()>,
42    message: StyledString,
43    width: usize,
44    anim_x: usize,
45    reversed: bool
46}
47
48impl<T: Send + Sync> LoadingAnimation<T> {
49    /// Create a new `LoadingAnimation` with the specified closure or function pointer as the task
50    pub fn new<U, M: Into<StyledString>>(message: M, task: U) -> LoadingAnimation<T>
51        where U: FnOnce() -> T + Send + Sync + 'static
52    {
53        let (sender, recv) = bounded(0);
54        let worker = Arc::new(RwLock::new(
55            Some(
56                thread::spawn(move || {
57                    let out = task();
58                    sender.send(()).unwrap();
59                    out
60                })
61            )
62        ));
63        let message = message.into();
64
65        LoadingAnimation {
66            worker,
67            recv,
68            width: message.char_len(),
69            message,
70            anim_x: 0,
71            reversed: false
72        }
73    }
74
75    /// Has the background task finished executing?
76    pub fn is_done(&self) -> bool {
77        match self.recv.try_recv() {
78            Ok(()) => true,
79            Err(e) => e == TryRecvError::Disconnected
80        }
81    }
82
83    /// Join with the task's thread, block until it is finished, and return the resulting value
84    /// 
85    /// It is best to run this when `LoadingAnimation::is_done()` is true
86    /// 
87    /// This will return `None` if called more than once
88    /// 
89    /// # Panics
90    /// This method will panic if the underlying task panicked while executing
91    #[track_caller]
92    pub fn finish(&mut self) -> Option<T> {
93        let worker = self.worker.write().unwrap().take()?;
94        Some(worker.join().unwrap())
95    }
96}
97
98impl<T: Send + Sync> View for LoadingAnimation<T> {
99    fn draw(&self, printer: &Printer) {
100        let style = ColorStyle::new(
101            PaletteColor::Highlight,
102            PaletteColor::Background,
103        );
104        printer.with_color(style, |printer| {
105            printer.print((self.anim_x, 0), "███");
106        });
107        printer.print_styled((0, 1), self.message.as_spanned_str());
108    }
109
110    fn layout(&mut self, _: Vec2) {
111        if self.width > 0 {
112            if self.reversed {
113                if self.anim_x == 0 {
114                    self.reversed = false;
115                }
116                else {
117                    self.anim_x -= 1;
118                }
119            }
120            else if self.anim_x < self.width - 3 {
121                self.anim_x += 1;
122            }
123            else {
124                self.reversed = true;
125            }
126        }
127    }
128
129    fn required_size(&mut self, _: Vec2) -> Vec2 { Vec2::new(self.message.char_len(), 2) }
130}