use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
use cursive_core::direction::Direction;
use cursive_core::event::{AnyCb, Event, EventResult};
use cursive_core::theme::PaletteColor;
use cursive_core::utils::markup::StyledString;
use cursive_core::view::{Selector, View};
use cursive_core::views::TextView;
use cursive_core::{Cursive, Printer, Rect, Vec2};
use interpolation::Ease;
use log::warn;
use num::clamp;
use send_wrapper::SendWrapper;
use std::thread;
use std::time::Instant;
use crate::{infinite::FPS, utils, AsyncView};
pub enum AsyncProgressState<V: View> {
Pending(f32),
Error(String),
Available(V),
}
pub struct AnimationProgressFrame {
pub content: StyledString,
pub pos: usize,
pub next_frame_idx: usize,
}
pub fn default_progress(
width: usize,
_height: usize,
progress: f32,
pos: usize,
frame_idx: usize,
) -> AnimationProgressFrame {
assert!(progress >= 0.0);
assert!(progress <= 1.0);
let foreground = PaletteColor::Highlight;
let background = PaletteColor::HighlightInactive;
let symbol = "━";
let duration = 30;
let durationf = duration as f64;
let next_pos = width as f32 * progress;
let offset = next_pos as usize - pos;
let idx = frame_idx % duration;
let idxf = idx as f64;
let factor = (idxf / durationf).circular_out();
let end = (pos as f64 + offset as f64 * factor) as usize;
let mut result = StyledString::new();
result.append_styled(utils::repeat_str(symbol, end), foreground);
result.append_styled(utils::repeat_str(symbol, width - end), background);
AnimationProgressFrame {
content: result,
pos: end,
next_frame_idx: idx + 1,
}
}
pub fn default_progress_error(
msg: String,
width: usize,
_height: usize,
progress: f32,
pos: usize,
frame_idx: usize,
) -> AnimationProgressFrame {
assert!(progress >= 0.0);
assert!(progress <= 1.0);
let foreground = PaletteColor::Highlight;
let background = PaletteColor::HighlightInactive;
let symbol = "━";
let duration = 30;
let durationf = duration as f64;
let idx = frame_idx;
let idxf = idx as f64;
let factor = (idxf / durationf).circular_in_out();
let mut offset = width as f64 * factor;
let padding = width.saturating_sub(msg.len()) / 2;
let mut background_content = format!(
"{}{}{}",
utils::repeat_str(" ", padding),
msg,
utils::repeat_str(" ", padding),
);
if background_content
.as_str()
.get(0..offset as usize)
.is_none()
{
offset += 2_f64;
}
let end = pos + offset as usize;
background_content.truncate(offset as usize);
let mut result = StyledString::new();
result.append_plain(background_content);
result.append_styled(
utils::repeat_str(symbol, {
if (pos + offset as usize) < width {
pos
} else {
width.saturating_sub(offset as usize)
}
}),
foreground,
);
result.append_styled(
utils::repeat_str(symbol, width.saturating_sub(end)),
background,
);
AnimationProgressFrame {
content: result,
pos,
next_frame_idx: frame_idx + 1,
}
}
pub struct AsyncProgressView<T: View> {
view: AsyncProgressState<T>,
loading: TextView,
progress_fn: Box<dyn Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static>,
error_fn:
Box<dyn Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static>,
width: Option<usize>,
height: Option<usize>,
view_rx: Receiver<AsyncProgressState<T>>,
frame_index: usize,
dropped: Sender<()>,
pos: usize,
}
impl<T: View> AsyncProgressView<T> {
pub fn new<F>(siv: &mut Cursive, creator: F) -> Self
where
F: FnMut() -> AsyncProgressState<T> + 'static,
{
let (view_tx, view_rx) = unbounded();
let (error_tx, error_rx) = bounded(1);
Self::polling_cb(
siv,
Instant::now(),
SendWrapper::new(view_tx),
error_rx,
creator,
);
Self {
view: AsyncProgressState::Pending(0.0),
loading: TextView::new(""),
progress_fn: Box::new(default_progress),
error_fn: Box::new(default_progress_error),
width: None,
height: None,
view_rx,
frame_index: 0,
dropped: error_tx,
pos: 0,
}
}
fn polling_cb<F>(
siv: &mut Cursive,
instant: Instant,
chan: SendWrapper<Sender<AsyncProgressState<T>>>,
error_chan: Receiver<()>,
mut cb: F,
) where
F: FnMut() -> AsyncProgressState<T> + 'static,
{
let res = cb();
match res {
AsyncProgressState::Pending(_) => {
let sink = siv.cb_sink().clone();
let cb = SendWrapper::new(cb);
match chan.send(res) {
Ok(_) => {},
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),
}
thread::spawn(move || {
if let Some(duration) = FPS.checked_sub(instant.elapsed()) {
thread::sleep(duration);
}
match sink.send(Box::new(move |siv| {
Self::polling_cb(siv, Instant::now(), chan, error_chan, cb.take())
})) {
Ok(_) => {}
Err(send_err) => {
warn!("Could not send callback to cursive. It probably has been dropped before the asynchronous initialization of a view has been finished: {}", send_err);
}
}
});
}
AsyncProgressState::Error(content) => {
AsyncView::<T>::error_anim_cb(siv, error_chan);
match chan.send(AsyncProgressState::Error(content)) {
Ok(_) => {}
Err(send_err) => {
warn!("View has been dropped before asynchronous initialization has been finished. Check if you removed this view from Cursive: {}", send_err);
}
}
}
AsyncProgressState::Available(view) => {
match chan.send(AsyncProgressState::Available(view)) {
Ok(_) => {}
Err(send_err) => {
warn!("View has been dropped before asynchronous initialization has been finished. Check if you removed this view from Cursive: {}", send_err);
}
}
}
}
}
pub fn with_width(mut self, width: usize) -> Self {
self.set_width(width);
self
}
pub fn with_height(mut self, height: usize) -> Self {
self.set_height(height);
self
}
pub fn with_progress_fn<F>(mut self, progress_fn: F) -> Self
where
F: Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static,
{
self.set_progress_fn(progress_fn);
self
}
pub fn with_error_fn<F>(mut self, error_fn: F) -> Self
where
F: Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static,
{
self.set_error_fn(error_fn);
self
}
pub fn set_width(&mut self, width: usize) {
self.width = Some(width);
}
pub fn set_height(&mut self, height: usize) {
self.height = Some(height);
}
pub fn set_progress_fn<F>(&mut self, progress_fn: F)
where
F: Fn(usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static,
{
self.progress_fn = Box::new(progress_fn);
}
pub fn set_error_fn<F>(&mut self, error_fn: F)
where
F: Fn(String, usize, usize, f32, usize, usize) -> AnimationProgressFrame + 'static,
{
self.error_fn = Box::new(error_fn);
}
pub fn inherit_width(&mut self) {
self.width = None;
}
pub fn inherit_height(&mut self) {
self.height = None;
}
}
impl<T: View> Drop for AsyncProgressView<T> {
fn drop(&mut self) {
match self.dropped.send(()) {
Ok(_) => {}
Err(send_err) => warn!(
"Refreshing thread has been dropped before view has, this has no impact on your code and is a bug: {}",
send_err
),
}
}
}
impl<T: View + Sized> View for AsyncProgressView<T> {
fn draw(&self, printer: &Printer) {
match &self.view {
AsyncProgressState::Available(v) => {
v.draw(printer);
}
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.draw(printer)
}
}
}
fn layout(&mut self, vec: Vec2) {
match &mut self.view {
AsyncProgressState::Available(v) => v.layout(vec),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.layout(vec)
}
}
}
fn needs_relayout(&self) -> bool {
match &self.view {
AsyncProgressState::Available(v) => v.needs_relayout(),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.needs_relayout()
}
}
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
if match self.view {
AsyncProgressState::Available(_) => false,
_ => true,
} {
if let Ok(state) = self.view_rx.try_recv() {
self.view = state
}
}
match &mut self.view {
AsyncProgressState::Available(v) => v.required_size(constraint),
AsyncProgressState::Pending(value) => {
let width = self.width.unwrap_or(constraint.x);
let height = self.height.unwrap_or(constraint.y);
let AnimationProgressFrame {
content,
pos,
next_frame_idx,
} = (self.progress_fn)(
width,
height,
clamp(*value, 0.0, 1.0),
self.pos,
self.frame_index,
);
self.pos = pos;
self.frame_index = next_frame_idx;
self.loading.set_content(content);
self.loading.required_size(constraint)
}
AsyncProgressState::Error(msg) => {
let width = self.width.unwrap_or(constraint.x);
let height = self.height.unwrap_or(constraint.y);
let AnimationProgressFrame {
content,
pos,
next_frame_idx,
} = (self.error_fn)(
(*msg).to_string(),
width,
height,
0.5,
self.pos,
self.frame_index,
);
self.pos = pos;
self.frame_index = next_frame_idx;
self.loading.set_content(content);
self.loading.required_size(constraint)
}
}
}
fn on_event(&mut self, ev: Event) -> EventResult {
match &mut self.view {
AsyncProgressState::Available(v) => v.on_event(ev),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.on_event(ev)
}
}
}
fn call_on_any<'a>(&mut self, sel: &Selector, cb: AnyCb<'a>) {
match &mut self.view {
AsyncProgressState::Available(v) => v.call_on_any(sel, cb),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.call_on_any(sel, cb)
}
}
}
fn focus_view(&mut self, sel: &Selector) -> Result<(), ()> {
match &mut self.view {
AsyncProgressState::Available(v) => v.focus_view(sel),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.focus_view(sel)
}
}
}
fn take_focus(&mut self, source: Direction) -> bool {
match &mut self.view {
AsyncProgressState::Available(v) => v.take_focus(source),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.take_focus(source)
}
}
}
fn important_area(&self, view_size: Vec2) -> Rect {
match &self.view {
AsyncProgressState::Available(v) => v.important_area(view_size),
AsyncProgressState::Error(_) | AsyncProgressState::Pending(_) => {
self.loading.important_area(view_size)
}
}
}
}