cursive_extras/views/
mod.rs

1use cursive_core::{
2    theme::{Color, ColorStyle, BaseColor},
3    views::{DummyView, ResizedView},
4    View,
5    Printer,
6    Vec2,
7    utils::markup::StyledString
8};
9use rust_utils::{encapsulated, new_default};
10use std::{
11    time::Instant,
12    fmt::Display
13};
14
15mod loading;
16pub use loading::LoadingAnimation;
17
18mod lazy_view;
19pub use lazy_view::LazyView;
20
21#[cfg(feature = "image_view")]
22mod image_view;
23
24#[cfg(feature = "image_view")]
25pub use image_view::ImageView;
26
27#[cfg(feature = "tabs")]
28mod tabs;
29
30#[cfg(feature = "tabs")]
31pub use tabs::*;
32
33#[cfg(feature = "advanced")]
34mod advanced;
35
36#[cfg(feature = "advanced")]
37pub use advanced::*;
38
39#[cfg(feature = "log_view")]
40mod log_view;
41
42#[cfg(feature = "log_view")]
43pub use log_view::*;
44
45use crate::SpannedStrExt;
46
47/// A spacer between 2 views
48///
49/// This is created by one of the spacer functions
50pub type Spacer = ResizedView<DummyView>;
51
52// size for a divider
53// either a fixed size or it fills the available space
54#[derive(Copy, Clone, Eq, PartialEq)]
55enum DividerSize {
56    Free,
57    Fixed(usize)
58}
59
60/// Horizontal divider view
61///
62/// # Example
63/// ```
64/// let mut root = cursive::default();
65/// root.add_fullscreen_layer(
66///     Dialog::around(
67///         hlayout!(
68///             select_view! {
69///                 "item1" => 1,
70///                 "item2" => 2,
71///                 "item3" => 3
72///             },
73///             HDivider::new(),
74///             select_view! {
75///                 "item4" => 4,
76///                 "item5" => 5,
77///                 "item6" => 6
78///              }
79///         )
80///     )
81///         .button("Quit", Cursive::quit)
82///         .title("Horizontal Divider Example")
83/// );
84/// root.run();
85/// ```
86#[derive(Copy, Clone)]
87pub struct HDivider(DividerSize, usize);
88
89impl HDivider {
90    /// Creates new horizontal divider that takes up the available height
91    pub fn new() -> HDivider { HDivider(DividerSize::Free, 0) }
92
93    /// Creates new horizontal divider with a fixed height
94    pub fn fixed(height: usize) -> HDivider {
95        HDivider(DividerSize::Fixed(height), height)
96    }
97
98    pub fn height(&self) -> usize {
99        self.1
100    }
101}
102
103impl View for HDivider {
104    fn draw(&self, printer: &Printer) {
105        let height = if let DividerSize::Fixed(height) = self.0 {
106            height
107        }
108        else { printer.size.y };
109
110        printer.with_high_border(false, |printer| printer.print_vline((0, 0), height, "│"));
111    }
112
113    fn required_size(&mut self, _bound: Vec2) -> Vec2 {
114        if let DividerSize::Fixed(height) = self.0 {
115            Vec2::new(1, height)
116        }
117        else {
118            Vec2::new(1, 1)
119        }
120    }
121
122    fn layout(&mut self, size: Vec2) {
123        if self.0 == DividerSize::Free {
124            self.1 = size.y;
125        }
126    }
127}
128
129new_default!(HDivider);
130
131/// Vertical divider view
132/// 
133/// # Example
134/// ```
135/// let mut root = cursive::default();
136/// root.add_fullscreen_layer(
137///     Dialog::around(
138///         vlayout!(
139///             select_view! {
140///                 "item1" => 1,
141///                 "item2" => 2,
142///                 "item3" => 3
143///             },
144///             VDivider::new(),
145///             select_view! {
146///                 "item4" => 4,
147///                 "item5" => 5,
148///                 "item6" => 6
149///             }
150///         )
151///     )
152///         .button("Quit", Cursive::quit)
153///         .title("Vertical Divider Example")
154/// );
155/// root.run();
156/// ```
157#[derive(Copy, Clone)]
158pub struct VDivider(DividerSize, usize);
159
160impl VDivider {
161    /// Creates new vertical divider that takes up the available width
162    pub fn new() -> VDivider { VDivider(DividerSize::Free, 0) }
163
164    /// Creates new vertical divider with a fixed width
165    pub fn fixed(width: usize) -> VDivider {
166        VDivider(DividerSize::Fixed(width), width)
167    }
168
169    pub fn width(&self) -> usize {
170        self.1
171    }
172}
173
174impl View for VDivider {
175    fn draw(&self, printer: &Printer) {
176        let width = if let DividerSize::Fixed(width) = self.0 {
177            width
178        }
179        else { printer.size.x };
180        printer.with_high_border(false, |printer| printer.print_hline((0, 0), width, "─"));
181    }
182
183    fn required_size(&mut self, _bound: Vec2) -> Vec2 {
184        if let DividerSize::Fixed(width) = self.0 {
185            Vec2::new(width, 1)
186        }
187        else {
188            Vec2::new(1, 1)
189        }
190    }
191
192    fn layout(&mut self, size: Vec2) {
193        if self.0 == DividerSize::Free {
194            self.1 = size.x;
195        }
196    }
197}
198
199new_default!(VDivider);
200
201/// View that can be used to report an application's status. 
202/// It is meant to be placed at the bottom of the main Cursive layer
203///
204/// Auto-refresh in cursive must be set by calling `Cursive::set_fps()`
205/// or `Cursive::set_autorefresh()` before using this view or it won't work
206/// 
207/// # Examples
208/// 
209/// ## Reporting Application Status
210/// ```
211/// let mut root = cursive::default();
212/// root.add_fullscreen_layer(
213///    vlayout!(
214///         Dialog::text("Yes")
215///             .button("Quit", Cursive::quit)
216///             .title("StatusView Example"),
217///         StatusView::new().with_name("status")
218///    )
219/// );
220/// root.set_fps(30);
221/// root.set_global_callback(Event::Refresh, |root| {
222///     let mut status = root.find_name::<StatusView>("status").expect("StatusView does not exist!");
223///     status.info("Application Status");
224/// });
225/// root.run();
226/// ```
227/// 
228/// ## Reporting an Error
229/// ```
230/// let mut root = cursive::default();
231/// root.add_fullscreen_layer(
232///     vlayout!(
233///         Dialog::text("Yes")
234///             .button("Quit", Cursive::quit)
235///             .title("StatusView Example"),
236///         StatusView::new().with_name("status")
237///     )
238/// );
239/// 
240/// root.set_fps(30);
241/// root.set_global_callback(Event::Refresh, |root| {
242///     let error: Result<&str, &str> = Err("Error: Houston, we have a problem!");
243///     let mut status = root.find_name::<StatusView>("status").unwrap();
244///     report_error!(status, error);
245/// });
246/// root.run();
247/// ```
248
249#[derive(Clone)]
250#[encapsulated]
251pub struct StatusView {
252    #[setter(use_into_impl, doc = "Set the label of the [`StatusView`]")]
253    #[chainable(use_into_impl)]
254    label: StyledString,
255
256    cur_msg: StyledString,
257    time: Instant,
258    error: bool
259}
260
261impl StatusView {
262    /// Create a new `StatusView`
263    pub fn new() -> StatusView {
264        StatusView {
265            label: StyledString::new(),
266            cur_msg: StyledString::new(),
267            time: Instant::now(),
268            error: false
269        }
270    }
271
272    /// Set the message in the `StatusView` with error formatting (bright red text)
273    pub fn report_error<T: Display>(&mut self, text: T) {
274        let err_style = ColorStyle::from(Color::Light(BaseColor::Red));
275        self.cur_msg = StyledString::styled(text.to_string(), err_style);
276        self.time = Instant::now();
277        self.error = true;
278    }
279
280    /// Set the message in the `StatusView`
281    pub fn info<T: Into<StyledString>>(&mut self, text: T) {
282        self.cur_msg = text.into();
283        self.time = Instant::now();
284    }
285}
286
287impl Default for StatusView {
288    fn default() -> StatusView { StatusView::new() }
289}
290
291impl View for StatusView {
292    fn draw(&self, printer: &Printer) {
293        if printer.size.x == 0 && printer.size.y == 0 {
294            return;
295        }
296
297        if self.cur_msg.is_empty() {
298            if !self.label.is_empty() {
299                printer.print_styled((0, 0), self.label.as_spanned_str());
300            }
301        }
302        else {
303            printer.print_styled((0, 0), self.cur_msg.as_spanned_str());
304        }
305    }
306
307    fn required_size(&mut self, bound: Vec2) -> Vec2 {
308        let y = (!self.cur_msg.is_empty() || !self.label.is_empty()) as usize;
309        Vec2::new(bound.x, y)
310    }
311
312    fn layout(&mut self, _: Vec2) {
313        if self.time.elapsed().as_secs() >= 5 {
314            self.cur_msg = StyledString::new();
315            self.time = Instant::now();
316            self.error = false;
317        }
318    }
319}