1use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
2use cursive_core::direction::Direction;
3use cursive_core::event::{AnyCb, Event, EventResult};
4use cursive_core::theme::PaletteColor;
5use cursive_core::utils::markup::StyledString;
6use cursive_core::view::{CannotFocus, Selector, View, ViewNotFound};
7use cursive_core::views::TextView;
8use cursive_core::{Cursive, Printer, Rect, Vec2};
9use interpolation::Ease;
10use log::warn;
11use num::clamp;
12use send_wrapper::SendWrapper;
13
14use std::thread;
15use std::time::Instant;
16
17use crate::{infinite::FPS, utils, AsyncView};
18
19pub enum AsyncProgressState<V: View> {
21 Pending(f32),
23 Error(String),
25 Available(V),
27}
28
29pub struct AnimationProgressFrame {
31 pub content: StyledString,
33 pub pos: usize,
35 pub next_frame_idx: usize,
37}
38
39pub fn default_progress(
84 width: usize,
85 _height: usize,
86 progress: f32,
87 pos: usize,
88 frame_idx: usize,
89) -> AnimationProgressFrame {
90 assert!(progress >= 0.0);
91 assert!(progress <= 1.0);
92
93 let foreground = PaletteColor::Highlight;
94 let background = PaletteColor::HighlightInactive;
95 let symbol = "━";
96
97 let duration = 30; let durationf = duration as f64;
99
100 let next_pos = width as f32 * progress;
101 let offset = next_pos as usize - pos;
102
103 let idx = frame_idx % duration;
104 let idxf = idx as f64;
105 let factor = (idxf / durationf).circular_out();
106 let end = (pos as f64 + offset as f64 * factor) as usize;
107
108 let mut result = StyledString::new();
109 result.append_styled(utils::repeat_str(symbol, end), foreground);
110 result.append_styled(utils::repeat_str(symbol, width - end), background);
111
112 AnimationProgressFrame {
113 content: result,
114 pos: end,
115 next_frame_idx: idx + 1,
116 }
117}
118
119pub fn default_progress_error(
161 msg: String,
162 width: usize,
163 _height: usize,
164 progress: f32,
165 pos: usize,
166 frame_idx: usize,
167) -> AnimationProgressFrame {
168 assert!(progress >= 0.0);
169 assert!(progress <= 1.0);
170
171 let foreground = PaletteColor::Highlight;
172 let background = PaletteColor::HighlightInactive;
173 let symbol = "━";
174
175 let duration = 30; let durationf = duration as f64;
177 let idx = frame_idx;
178 let idxf = idx as f64;
179 let factor = (idxf / durationf).circular_in_out();
180 let mut offset = width as f64 * factor;
181
182 let padding = width.saturating_sub(msg.len()) / 2;
183 let mut background_content = format!(
184 "{}{}{}",
185 utils::repeat_str(" ", padding),
186 msg,
187 utils::repeat_str(" ", padding),
188 );
189 if background_content
191 .as_str()
192 .get(0..offset as usize)
193 .is_none()
194 {
195 offset += 2_f64;
196 }
197 let end = pos + offset as usize;
198 background_content.truncate(offset as usize);
199 let mut result = StyledString::new();
200 result.append_plain(background_content);
201 result.append_styled(
202 utils::repeat_str(symbol, {
203 if (pos + offset as usize) < width {
204 pos
205 } else {
206 width.saturating_sub(offset as usize)
207 }
208 }),
209 foreground,
210 );
211 result.append_styled(
212 utils::repeat_str(symbol, width.saturating_sub(end)),
213 background,
214 );
215
216 AnimationProgressFrame {
217 content: result,
218 pos,
219 next_frame_idx: frame_idx + 1,
220 }
221}
222
223pub struct AsyncProgressView<T: View> {
261 view: AsyncProgressState<T>,
262 loading: TextView,
263 progress_fn: Box<
264 dyn Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + Send + Sync + 'static,
265 >,
266 error_fn: Box<
267 dyn Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame
268 + Send
269 + Sync
270 + 'static,
271 >,
272 width: Option<usize>,
273 height: Option<usize>,
274 view_rx: Receiver<AsyncProgressState<T>>,
275 frame_index: usize,
276 dropped: Sender<()>,
277 pos: usize,
278}
279
280impl<T: View> AsyncProgressView<T> {
281 pub fn new<F>(siv: &mut Cursive, creator: F) -> Self
289 where
290 F: FnMut() -> AsyncProgressState<T> + 'static,
291 {
292 let (view_tx, view_rx) = unbounded();
293 let (error_tx, error_rx) = bounded(1);
294
295 Self::polling_cb(
296 siv,
297 Instant::now(),
298 SendWrapper::new(view_tx),
299 error_rx,
300 creator,
301 );
302
303 Self {
304 view: AsyncProgressState::Pending(0.0),
305 loading: TextView::new(""),
306 progress_fn: Box::new(default_progress),
307 error_fn: Box::new(default_progress_error),
308 width: None,
309 height: None,
310 view_rx,
311 frame_index: 0,
312 dropped: error_tx,
313 pos: 0,
314 }
315 }
316
317 fn polling_cb<F>(
318 siv: &mut Cursive,
319 instant: Instant,
320 chan: SendWrapper<Sender<AsyncProgressState<T>>>,
321 error_chan: Receiver<()>,
322 mut cb: F,
323 ) where
324 F: FnMut() -> AsyncProgressState<T> + 'static,
325 {
326 let res = cb();
327 match res {
328 AsyncProgressState::Pending(_) => {
329 let sink = siv.cb_sink().clone();
330 let cb = SendWrapper::new(cb);
331 match chan.send(res) {
332 Ok(_) => {},
333 Err(send_err) => warn!("Could not send progress to AsyncProgressView. It probably has been dropped before the asynchronous initialization of a view has been finished: {}", send_err),
334 }
335 thread::spawn(move || {
336 if let Some(duration) = FPS.checked_sub(instant.elapsed()) {
338 thread::sleep(duration);
339 }
340
341 match sink.send(Box::new(move |siv| {
342 Self::polling_cb(siv, Instant::now(), chan, error_chan, cb.take())
343 })) {
344 Ok(_) => {}
345 Err(send_err) => {
346 warn!("Could not send callback to cursive. It probably has been dropped before the asynchronous initialization of a view has been finished: {}", send_err);
347 }
348 }
349 });
350 }
351 AsyncProgressState::Error(content) => {
352 AsyncView::<T>::error_anim_cb(siv, error_chan);
353
354 match chan.send(AsyncProgressState::Error(content)) {
355 Ok(_) => {}
356 Err(send_err) => {
357 warn!("View has been dropped before asynchronous initialization has been finished. Check if you removed this view from Cursive: {}", send_err);
358 }
359 }
360 }
362 AsyncProgressState::Available(view) => {
363 match chan.send(AsyncProgressState::Available(view)) {
364 Ok(_) => {}
365 Err(send_err) => {
366 warn!("View has been dropped before asynchronous initialization has been finished. Check if you removed this view from Cursive: {}", send_err);
367 }
368 }
369 }
370 }
371 }
372
373 pub fn with_width(mut self, width: usize) -> Self {
376 self.set_width(width);
377 self
378 }
379
380 pub fn with_height(mut self, height: usize) -> Self {
383 self.set_height(height);
384 self
385 }
386
387 pub fn with_progress_fn<F>(mut self, progress_fn: F) -> Self
391 where
392 F: Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + Send + Sync + 'static,
393 {
394 self.set_progress_fn(progress_fn);
395 self
396 }
397
398 pub fn with_error_fn<F>(mut self, error_fn: F) -> Self
399 where
400 F: Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame
401 + Send
402 + Sync
403 + 'static,
404 {
405 self.set_error_fn(error_fn);
406 self
407 }
408
409 pub fn set_width(&mut self, width: usize) {
411 self.width = Some(width);
412 }
413
414 pub fn set_height(&mut self, height: usize) {
416 self.height = Some(height);
417 }
418
419 pub fn set_progress_fn<F>(&mut self, progress_fn: F)
426 where
427 F: Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + Send + Sync + 'static,
428 {
429 self.progress_fn = Box::new(progress_fn);
430 }
431
432 pub fn set_error_fn<F>(&mut self, error_fn: F)
439 where
440 F: Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame
441 + Send
442 + Sync
443 + 'static,
444 {
445 self.error_fn = Box::new(error_fn);
446 }
447
448 pub fn inherit_width(&mut self) {
450 self.width = None;
451 }
452
453 pub fn inherit_height(&mut self) {
455 self.height = None;
456 }
457}
458
459impl<T: View> Drop for AsyncProgressView<T> {
460 fn drop(&mut self) {
461 match self.dropped.send(()) {
462 Ok(_) => {}
463 Err(send_err) => warn!(
464 "Refreshing thread has been dropped before view has, this has no impact on your code and is a bug: {}",
465 send_err
466 ),
467 }
468 }
469}
470
471impl<T: View + Sized> View for AsyncProgressView<T> {
472 fn draw(&self, printer: &Printer) {
473 match &self.view {
474 AsyncProgressState::Available(v) => {
475 v.draw(printer);
476 }
477 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
478 self.loading.draw(printer)
479 }
480 }
481 }
482
483 fn layout(&mut self, vec: Vec2) {
484 match &mut self.view {
485 AsyncProgressState::Available(v) => v.layout(vec),
486 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
487 self.loading.layout(vec)
488 }
489 }
490 }
491
492 fn needs_relayout(&self) -> bool {
493 match &self.view {
494 AsyncProgressState::Available(v) => v.needs_relayout(),
495 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
496 self.loading.needs_relayout()
497 }
498 }
499 }
500
501 fn required_size(&mut self, constraint: Vec2) -> Vec2 {
502 if !matches!(self.view, AsyncProgressState::Available(_)) {
503 if let Ok(state) = self.view_rx.try_recv() {
504 self.view = state
505 }
506 }
507
508 match &mut self.view {
509 AsyncProgressState::Available(v) => v.required_size(constraint),
510 AsyncProgressState::Pending(value) => {
511 let width = self.width.unwrap_or(constraint.x);
512 let height = self.height.unwrap_or(constraint.y);
513 let AnimationProgressFrame {
514 content,
515 pos,
516 next_frame_idx,
517 } = (self.progress_fn)(
518 width,
519 height,
520 clamp(*value, 0.0, 1.0),
521 self.pos,
522 self.frame_index,
523 );
524 self.pos = pos;
525 self.frame_index = next_frame_idx;
526 self.loading.set_content(content);
527 self.loading.required_size(constraint)
528 }
529 AsyncProgressState::Error(msg) => {
530 let width = self.width.unwrap_or(constraint.x);
531 let height = self.height.unwrap_or(constraint.y);
532 let AnimationProgressFrame {
533 content,
534 pos,
535 next_frame_idx,
536 } = (self.error_fn)(
537 (*msg).to_string(),
538 width,
539 height,
540 0.5,
541 self.pos,
542 self.frame_index,
543 );
544 self.pos = pos;
545 self.frame_index = next_frame_idx;
546 self.loading.set_content(content);
547 self.loading.required_size(constraint)
548 }
549 }
550 }
551
552 fn on_event(&mut self, ev: Event) -> EventResult {
553 match &mut self.view {
554 AsyncProgressState::Available(v) => v.on_event(ev),
555 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
556 self.loading.on_event(ev)
557 }
558 }
559 }
560
561 fn call_on_any<'a>(&mut self, sel: &Selector, cb: AnyCb<'a>) {
562 match &mut self.view {
563 AsyncProgressState::Available(v) => v.call_on_any(sel, cb),
564 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
565 self.loading.call_on_any(sel, cb)
566 }
567 }
568 }
569
570 fn focus_view(&mut self, sel: &Selector) -> Result<EventResult, ViewNotFound> {
571 match &mut self.view {
572 AsyncProgressState::Available(v) => v.focus_view(sel),
573 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
574 self.loading.focus_view(sel)
575 }
576 }
577 }
578
579 fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
580 match &mut self.view {
581 AsyncProgressState::Available(v) => v.take_focus(source),
582 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
583 self.loading.take_focus(source)
584 }
585 }
586 }
587
588 fn important_area(&self, view_size: Vec2) -> Rect {
589 match &self.view {
590 AsyncProgressState::Available(v) => v.important_area(view_size),
591 AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
592 self.loading.important_area(view_size)
593 }
594 }
595 }
596}