use std::thread;
use std::time::{Duration, Instant};
use crossbeam::channel::{self, Receiver, Sender, TryRecvError};
use cursive_core::align::HAlign;
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::{CannotFocus, Selector, View, ViewNotFound};
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 crate::utils;
pub struct AnimationFrame {
pub content: StyledString,
pub next_frame_idx: usize,
}
pub fn default_animation(width: usize, _height: usize, frame_idx: usize) -> AnimationFrame {
let foreground = PaletteColor::Highlight;
let background = PaletteColor::HighlightInactive;
let symbol = "━";
let duration = 60; let durationf = duration as f64;
let idx = frame_idx % duration;
let idxf = idx as f64;
let factor = idxf / durationf;
let begin_factor = clamp((factor % 1.0).circular_in_out(), 0.0, 1.0);
let end_factor = clamp(((factor + 0.25) % 1.0).circular_in_out() * 2.0, 0.0, 1.0);
let begin = (begin_factor * width as f64) as usize;
let end = (end_factor * width as f64) as usize;
let mut result = StyledString::default();
if end >= begin {
result.append_styled(utils::repeat_str(symbol, begin), background);
result.append_styled(utils::repeat_str(symbol, end - begin), foreground);
result.append_styled(utils::repeat_str(symbol, width - end), background);
} else {
result.append_styled(utils::repeat_str(symbol, end), foreground);
result.append_styled(utils::repeat_str(symbol, begin - end), background);
result.append_styled(utils::repeat_str(symbol, width - begin), foreground);
}
AnimationFrame {
content: result,
next_frame_idx: (idx + 1) % duration,
}
}
pub fn default_error(
msg: &str,
width: usize,
_height: usize,
error_idx: usize,
frame_idx: usize,
) -> AnimationFrame {
let foreground = PaletteColor::Highlight;
let background = PaletteColor::HighlightInactive;
let symbol = "━";
let offset = utils::repeat_str(" ", HAlign::Center.get_offset(msg.len(), width));
let mut msg = format!("{}{}{}", offset, msg, offset);
let duration = 60; let durationf = duration as f64;
let cycle = if error_idx % duration > duration / 2 {
duration
} else {
0
};
let idx = frame_idx - (error_idx / duration) * duration;
let idxf = idx as f64;
let factor = idxf / durationf;
let begin_factor = clamp((factor % 1.0).circular_in_out(), 0.0, 1.0);
let end_factor = clamp(((factor + 0.25) % 1.0).circular_in_out() * 2.0, 0.0, 1.0);
let mut begin = (begin_factor * width as f64) as usize;
let end = (end_factor * width as f64) as usize;
if frame_idx == cycle + duration {
return AnimationFrame {
content: StyledString::plain(msg),
next_frame_idx: frame_idx,
};
}
let mut result = StyledString::default();
if end >= begin && idx > cycle {
if msg.as_str().get(0..begin).is_none() {
begin += 2;
}
msg.truncate(begin);
result.append_plain(msg);
result.append_styled(utils::repeat_str(symbol, end - begin), foreground);
result.append_styled(utils::repeat_str(symbol, width - end), background);
} else if end >= begin && idx <= cycle {
result.append_styled(utils::repeat_str(symbol, begin), background);
result.append_styled(utils::repeat_str(symbol, end - begin), foreground);
result.append_styled(utils::repeat_str(symbol, width - end), background);
} else if idx > cycle + duration / 2 {
if msg.as_str().get(0..begin).is_none() {
begin += 2
}
msg.truncate(begin);
result.append_plain(msg);
result.append_styled(utils::repeat_str(symbol, width - begin), foreground);
} else {
result.append_styled(utils::repeat_str(symbol, end), foreground);
result.append_styled(utils::repeat_str(symbol, begin - end), background);
result.append_styled(utils::repeat_str(symbol, width - begin), foreground);
}
AnimationFrame {
content: result,
next_frame_idx: frame_idx + 1,
}
}
pub enum AsyncState<V: View> {
Available(V),
Error(String),
Pending,
}
pub struct AsyncView<T: View> {
view: AsyncState<T>,
loading: TextView,
animation_fn: Box<dyn Fn(usize, usize, usize) -> AnimationFrame + Send + Sync + 'static>,
error_fn:
Box<dyn Fn(&str, usize, usize, usize, usize) -> AnimationFrame + Send + Sync + 'static>,
width: Option<usize>,
height: Option<usize>,
pos: usize,
error_idx: usize,
rx: Receiver<AsyncState<T>>,
error_sender: Sender<()>,
}
lazy_static::lazy_static! {
pub(crate) static ref FPS: Duration = Duration::from_secs(1) / 60;
}
impl<T: View> AsyncView<T> {
pub fn new<F>(siv: &mut Cursive, ready_poll: F) -> Self
where
F: FnMut() -> AsyncState<T> + 'static,
{
let (tx, rx) = channel::unbounded();
let (error_tx, error_rx) = channel::bounded(1);
let instant = Instant::now();
Self::polling_cb(siv, instant, SendWrapper::new(tx), error_rx, ready_poll);
Self {
view: AsyncState::Pending,
loading: TextView::new(""),
animation_fn: Box::new(default_animation),
error_fn: Box::new(default_error),
width: None,
height: None,
pos: 0,
error_idx: 0,
rx,
error_sender: error_tx,
}
}
pub fn new_with_bg_creator<F, C, D>(siv: &mut Cursive, bg_task: F, mut view_creator: C) -> Self
where
D: Send + 'static,
F: FnOnce() -> Result<D, String> + Send + 'static,
C: FnMut(D) -> T + 'static,
{
let (tx, rx) = channel::unbounded();
thread::Builder::new()
.name("cursive-async-view::bg_task".into())
.spawn(move || {
tx.send(bg_task()).unwrap();
})
.unwrap();
Self::new(siv, move || match rx.try_recv() {
Ok(Ok(data)) => AsyncState::Available(view_creator(data)),
Ok(Err(err)) => AsyncState::Error(err),
Err(TryRecvError::Empty) => AsyncState::Pending,
Err(TryRecvError::Disconnected) => {
AsyncState::Error("Internal error: bg_task disconnected unexpectedly!".to_string())
}
})
}
fn polling_cb<F>(
siv: &mut Cursive,
instant: Instant,
chan: SendWrapper<Sender<AsyncState<T>>>,
end_anim: Receiver<()>,
mut cb: F,
) where
F: FnMut() -> AsyncState<T> + 'static,
{
match cb() {
AsyncState::Pending => {
let sink = siv.cb_sink().clone();
let cb = SendWrapper::new(cb);
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, end_anim, 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);
}
}
});
}
AsyncState::Error(content) => {
Self::error_anim_cb(siv, end_anim);
match chan.send(AsyncState::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);
}
}
}
AsyncState::Available(view) => match chan.send(AsyncState::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(crate) fn error_anim_cb(siv: &mut Cursive, chan: Receiver<()>) {
let sink = siv.cb_sink().clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(16));
match chan.try_recv() {
Ok(()) => break,
Err(_) => match sink.send(Box::new(|_| {})) {
Ok(_) => {}
Err(send_err) => {
warn!(
"Cursive has been dropped before AsyncView has been: {}",
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_animation_fn<F>(mut self, animation_fn: F) -> Self
where
F: Fn(usize, usize, usize) -> AnimationFrame + Send + Sync + 'static,
{
self.set_animation_fn(animation_fn);
self
}
pub fn with_error_fn<F>(mut self, error_fn: F) -> Self
where
F: Fn(&str, usize, usize, usize, usize) -> AnimationFrame + Send + Sync + '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_animation_fn<F>(&mut self, animation_fn: F)
where
F: Fn(usize, usize, usize) -> AnimationFrame + Send + Sync + 'static,
{
self.animation_fn = Box::new(animation_fn);
}
pub fn set_error_fn<F>(&mut self, error_fn: F)
where
F: Fn(&str, usize, usize, usize, usize) -> AnimationFrame + Send + Sync + '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 AsyncView<T> {
fn drop(&mut self) {
match self.error_sender.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 AsyncView<T> {
fn draw(&self, printer: &Printer) {
match self.view {
AsyncState::Available(ref view) => view.draw(printer),
_ => self.loading.draw(printer),
}
}
fn layout(&mut self, vec: Vec2) {
match self.view {
AsyncState::Available(ref mut view) => view.layout(vec),
_ => self.loading.layout(vec),
}
}
fn needs_relayout(&self) -> bool {
match self.view {
AsyncState::Available(ref view) => view.needs_relayout(),
_ => true,
}
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
match self.rx.try_recv() {
Ok(view) => {
if let AsyncState::Error(_) = view {
self.error_idx = self.pos;
}
self.view = view;
}
Err(TryRecvError::Empty) => {
}
Err(TryRecvError::Disconnected) => {
}
}
match self.view {
AsyncState::Available(ref mut view) => view.required_size(constraint),
AsyncState::Error(ref msg) => {
let width = self.width.unwrap_or(constraint.x);
let height = self.height.unwrap_or(constraint.y);
let AnimationFrame {
content,
next_frame_idx,
} = (self.error_fn)(msg, width, height, self.error_idx, self.pos);
self.loading.set_content(content);
self.pos = next_frame_idx;
self.loading.required_size(constraint)
}
AsyncState::Pending => {
let width = self.width.unwrap_or(constraint.x);
let height = self.height.unwrap_or(constraint.y);
let AnimationFrame {
content,
next_frame_idx,
} = (self.animation_fn)(width, height, self.pos);
self.loading.set_content(content);
self.pos = next_frame_idx;
self.loading.required_size(constraint)
}
}
}
fn on_event(&mut self, ev: Event) -> EventResult {
match self.view {
AsyncState::Available(ref mut view) => view.on_event(ev),
_ => EventResult::Ignored,
}
}
fn call_on_any<'a>(&mut self, sel: &Selector, cb: AnyCb<'a>) {
if let AsyncState::Available(ref mut view) = self.view {
view.call_on_any(sel, cb)
}
}
fn focus_view(&mut self, sel: &Selector) -> Result<EventResult, ViewNotFound> {
match self.view {
AsyncState::Available(ref mut view) => view.focus_view(sel),
_ => Err(ViewNotFound),
}
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
match self.view {
AsyncState::Available(ref mut view) => view.take_focus(source),
_ => Err(CannotFocus),
}
}
fn important_area(&self, view_size: Vec2) -> Rect {
match self.view {
AsyncState::Available(ref view) => view.important_area(view_size),
_ => self.loading.important_area(view_size),
}
}
}