photon_ui/components/
cancellable_loader.rs1use std::sync::Arc;
2
3use crate::{
4 Component,
5 Event,
6 InputResult,
7 RenderError,
8 Rendered,
9 components::Loader,
10};
11
12pub struct CancellableLoader {
19 loader: Loader,
20 signal: Arc<std::sync::atomic::AtomicBool>,
21}
22
23impl CancellableLoader {
24 pub fn new(
26 message: impl Into<String>,
27 spinner_color: Option<String>,
28 message_color: Option<String>,
29 ) -> Self {
30 Self {
31 loader: Loader::new(message, spinner_color, message_color),
32 signal: Arc::new(std::sync::atomic::AtomicBool::new(false)),
33 }
34 }
35
36 pub fn cancel(&self) {
38 self.signal
39 .store(true, std::sync::atomic::Ordering::Relaxed);
40 }
41
42 pub fn is_cancelled(&self) -> bool {
44 self.signal.load(std::sync::atomic::Ordering::Relaxed)
45 }
46
47 pub fn tick(&mut self) {
49 self.loader.tick();
50 }
51}
52
53impl Component for CancellableLoader {
54 fn render(&self, width: u16) -> Result<Rendered, RenderError> {
55 self.loader.render(width)
56 }
57
58 fn handle_input(&mut self, event: &Event) -> InputResult {
59 use crate::events::matches_key;
60 if matches_key(event, &crate::events::Key::ctrl('c')) {
61 self.cancel();
62 InputResult::Handled
63 } else {
64 InputResult::Ignored
65 }
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use crossterm::event::{
72 KeyCode,
73 KeyEvent,
74 KeyModifiers,
75 };
76
77 use super::*;
78
79 #[test]
80 fn render_delegates() {
81 let cl = CancellableLoader::new("test", None, None);
82 let r = cl.render(80).unwrap();
83 assert!(r.lines[0].contains("test"));
84 }
85
86 #[test]
87 fn ignored_key() {
88 let mut cl = CancellableLoader::new("test", None, None);
89 let result = cl.handle_input(&Event::Key(KeyEvent::new(
90 KeyCode::Char('x'),
91 KeyModifiers::empty(),
92 )));
93 assert!(matches!(result, InputResult::Ignored));
94 assert!(!cl.is_cancelled());
95 }
96}