use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use crate::components::Loader;
use crate::{Color, Component, Event, KeyCode, Rect, Size, Surface};
pub type OnAbortFn = Box<dyn Fn() + Send>;
pub struct CancellableLoader {
inner: Loader,
aborted: Arc<AtomicBool>,
on_abort: Option<OnAbortFn>,
}
impl CancellableLoader {
pub fn new(message: impl Into<String>) -> Self {
Self {
inner: Loader::new().with_message(message),
aborted: Arc::new(AtomicBool::new(false)),
on_abort: None,
}
}
pub fn with_color(message: impl Into<String>, color: Color) -> Self {
Self {
inner: Loader::new().with_message(message).with_color(color),
aborted: Arc::new(AtomicBool::new(false)),
on_abort: None,
}
}
pub fn on_abort(mut self, f: impl Fn() + Send + 'static) -> Self {
self.on_abort = Some(Box::new(f));
self
}
pub fn abort_flag(&self) -> Arc<AtomicBool> {
self.aborted.clone()
}
pub fn is_aborted(&self) -> bool {
self.aborted.load(Ordering::Relaxed)
}
pub fn abort(&mut self) {
self.aborted.store(true, Ordering::Relaxed);
self.inner.cancel();
if let Some(ref cb) = self.on_abort {
cb();
}
}
pub fn reset(&mut self) {
self.aborted.store(false, Ordering::Relaxed);
self.inner.reset();
}
pub fn set_message(&mut self, message: impl Into<String>) {
self.inner.set_message(message);
}
pub fn tick(&mut self) {
if !self.is_aborted() {
self.inner.tick();
}
}
pub fn set_done(&mut self, msg: impl Into<String>) {
self.inner.set_done(msg);
}
}
impl Default for CancellableLoader {
fn default() -> Self {
Self::new("")
}
}
impl Component for CancellableLoader {
fn name(&self) -> &str {
"CancellableLoader"
}
fn request_render(&mut self) {
self.inner.request_render();
}
fn is_dirty(&self) -> bool {
self.inner.is_dirty()
}
fn clear_dirty(&mut self) {
self.inner.clear_dirty();
}
fn handle_event(&mut self, event: &Event) -> bool {
if self.is_aborted() {
return false;
}
if let Event::Key(key) = event {
match key.code {
KeyCode::Escape => {
self.abort();
return true;
}
KeyCode::Char('c') if key.modifiers.ctrl => {
self.abort();
return true;
}
_ => {}
}
}
false
}
fn render(&mut self, surface: &mut Surface, area: Rect) {
self.inner.render(surface, area);
}
fn min_size(&self) -> Size {
self.inner.min_size()
}
fn on_focus(&mut self) {
self.inner.on_focus();
}
fn on_unfocus(&mut self) {
self.inner.on_unfocus();
}
fn is_focused(&self) -> bool {
self.inner.is_focused()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_loader() {
let loader = CancellableLoader::new("Working...");
assert!(!loader.is_aborted());
}
#[test]
fn test_abort() {
let mut loader = CancellableLoader::new("Working...");
assert!(!loader.is_aborted());
loader.abort();
assert!(loader.is_aborted());
}
#[test]
fn test_abort_flag() {
let loader = CancellableLoader::new("Working...");
let flag = loader.abort_flag();
assert!(!flag.load(Ordering::Relaxed));
drop(loader);
assert!(!flag.load(Ordering::Relaxed));
}
#[test]
fn test_reset() {
let mut loader = CancellableLoader::new("Working...");
loader.abort();
assert!(loader.is_aborted());
loader.reset();
assert!(!loader.is_aborted());
}
#[test]
fn test_handle_escape() {
let mut loader = CancellableLoader::new("Working...");
let event = Event::Key(crate::KeyEvent::new(KeyCode::Escape));
assert!(loader.handle_event(&event));
assert!(loader.is_aborted());
}
#[test]
fn test_handle_ctrl_c() {
let mut loader = CancellableLoader::new("Working...");
let event = Event::Key(crate::KeyEvent::with_modifiers(
KeyCode::Char('c'),
crate::event::KeyModifiers::new().with_ctrl(),
));
assert!(loader.handle_event(&event));
assert!(loader.is_aborted());
}
#[test]
fn test_ignore_events_after_abort() {
let mut loader = CancellableLoader::new("Working...");
loader.abort();
let event = Event::Key(crate::KeyEvent::new(KeyCode::Escape));
assert!(!loader.handle_event(&event));
}
#[test]
fn test_abort_callback() {
use std::sync::atomic::AtomicUsize;
static CALLED: AtomicUsize = AtomicUsize::new(0);
let mut loader = CancellableLoader::new("Working...")
.on_abort(|| {
CALLED.fetch_add(1, Ordering::SeqCst);
});
loader.abort();
assert_eq!(CALLED.load(Ordering::SeqCst), 1);
}
#[test]
fn test_set_message() {
let mut loader = CancellableLoader::new("Working...");
loader.set_message("New message");
}
#[test]
fn test_tick() {
let mut loader = CancellableLoader::new("Working...");
loader.tick();
}
#[test]
fn test_tick_aborted() {
let mut loader = CancellableLoader::new("Working...");
loader.abort();
loader.tick();
}
}