rpi_tui/
bordered_loader.rs1use std::any::Any;
10use std::sync::Arc;
11
12use super::component::Component;
13use crate::dynamic_border::DynamicBorder;
14use crate::keybinding_hints::key_hint;
15use crate::loader::{CancellableLoader, Loader};
16use crate::spacer::Spacer;
17use crate::theme::{theme, Color};
18
19pub enum BorderedLoaderInner {
21 Plain(Arc<Loader>),
23 Cancellable(Arc<CancellableLoader>),
25}
26
27pub struct BorderedLoader {
30 inner: BorderedLoaderInner,
31 border_color: Option<Color>,
32}
33
34impl BorderedLoader {
35 pub fn plain(loader: Arc<Loader>) -> Self {
37 Self {
38 inner: BorderedLoaderInner::Plain(loader),
39 border_color: None,
40 }
41 }
42
43 pub fn cancellable(loader: Arc<CancellableLoader>) -> Self {
45 Self {
46 inner: BorderedLoaderInner::Cancellable(loader),
47 border_color: None,
48 }
49 }
50
51 pub fn with_border_color(mut self, color: Color) -> Self {
53 self.border_color = Some(color);
54 self
55 }
56
57 fn border(&self) -> DynamicBorder {
58 match self.border_color {
59 Some(c) => DynamicBorder::with_color(c),
60 None => DynamicBorder::new(),
61 }
62 }
63
64 fn is_cancellable_running(&self) -> bool {
65 match &self.inner {
66 BorderedLoaderInner::Cancellable(l) => l.is_running(),
67 _ => false,
68 }
69 }
70}
71
72impl Component for BorderedLoader {
73 fn render(&self, width: usize) -> Vec<String> {
74 let _ = theme(); let mut lines = Vec::new();
76
77 lines.extend(self.border().render(width));
79
80 let body = match &self.inner {
82 BorderedLoaderInner::Plain(l) => l.render(width),
83 BorderedLoaderInner::Cancellable(l) => l.render(width),
84 };
85 lines.extend(body);
86
87 if self.is_cancellable_running() {
90 lines.extend(Spacer::new(1).render(width));
91 lines.push(key_hint("tui.select.cancel", "cancel"));
92 }
93
94 lines.extend(Spacer::new(1).render(width));
96 lines.extend(self.border().render(width));
97
98 lines
99 }
100
101 fn invalidate(&self) {
102 match &self.inner {
103 BorderedLoaderInner::Plain(l) => l.invalidate(),
104 BorderedLoaderInner::Cancellable(l) => l.invalidate(),
105 }
106 }
107
108 fn as_any(&self) -> &dyn Any {
109 self
110 }
111}
112
113#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_plain_bordered_loader_renders_borders() {
121 let loader = Arc::new(Loader::with_text("Working"));
122 loader.start();
123 let bl = BorderedLoader::plain(loader);
124 let lines = bl.render(40);
125 assert!(lines.len() >= 3);
127 assert!(lines.first().unwrap().contains('─'));
128 assert!(lines.last().unwrap().contains('─'));
129 }
130
131 #[test]
132 fn test_cancellable_adds_hint_when_running() {
133 let loader = Arc::new(CancellableLoader::with_text("Working"));
134 loader.start();
135 let bl = BorderedLoader::cancellable(loader);
136 let lines = bl.render(40);
137 let joined = lines.join("\n");
138 assert!(joined.contains("cancel"));
139 }
140}