use std::thread;
use std::time::{Duration, Instant};
use crossbeam::channel::{self, Receiver, Sender, TryRecvError};
use cursive::align::HAlign;
use cursive::direction::Direction;
use cursive::event::{AnyCb, Event, EventResult};
use cursive::theme::PaletteColor;
use cursive::utils::markup::StyledString;
use cursive::view::{Selector, View};
use cursive::views::TextView;
use cursive::{Cursive, Printer, Rect, Vec2};
use interpolation::Ease;
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 = 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 = 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 + 'static>,
error_fn: Box<dyn Fn(&str, usize, usize, usize, usize) -> AnimationFrame + 'static>,
width: Option<usize>,
height: Option<usize>,
pos: usize,
error_idx: usize,
rx: Receiver<AsyncState<T>>,
}
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 instant = Instant::now();
Self::polling_cb(siv, instant, SendWrapper::new(tx), 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,
}
}
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>>>,
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);
}
sink.send(Box::new(move |siv| {
Self::polling_cb(siv, Instant::now(), chan, cb.take())
}))
.unwrap();
});
}
state => {
let sink = siv.cb_sink().clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(16));
sink.send(Box::new(|_| {})).unwrap();
});
chan.send(state).unwrap();
}
}
}
pub fn with_width(self, width: usize) -> Self {
Self {
width: Some(width),
..self
}
}
pub fn with_height(self, height: usize) -> Self {
Self {
height: Some(height),
..self
}
}
pub fn with_animation_fn<F>(self, animation_fn: F) -> Self
where
F: Fn(usize, usize, usize) -> AnimationFrame + 'static,
{
Self {
animation_fn: Box::new(animation_fn),
..self
}
}
pub fn with_error_fn<F>(self, error_fn: F) -> Self
where
F: Fn(&str, usize, usize, usize, usize) -> AnimationFrame + 'static,
{
Self {
error_fn: Box::new(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 + '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 + '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 + 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>) {
match self.view {
AsyncState::Available(ref mut view) => view.call_on_any(sel, cb),
_ => {}
}
}
fn focus_view(&mut self, sel: &Selector) -> Result<(), ()> {
match self.view {
AsyncState::Available(ref mut view) => view.focus_view(sel),
_ => Err(()),
}
}
fn take_focus(&mut self, source: Direction) -> bool {
match self.view {
AsyncState::Available(ref mut view) => view.take_focus(source),
_ => false,
}
}
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),
}
}
}