cursive_extras/
funcs.rs

1use crate::{
2    hlayout,
3    views::{LoadingAnimation, Spacer}
4};
5use std::fmt::Display;
6use cursive_core::{
7    Cursive, With,
8    view::Nameable,
9    event::{Event, Key},
10    views::{
11        EditView,
12        Dialog,
13        OnEventView,
14        NamedView,
15        Checkbox,
16        LinearLayout,
17        TextView,
18        DummyView,
19        ResizedView
20    },
21    utils::markup::StyledString,
22    theme::{
23        PaletteColor,
24        Color,
25        BaseColor,
26        ColorType,
27        ColorStyle,
28        BorderStyle,
29        Theme
30    }
31};
32
33/// Convenience function that generates a better looking Cursive theme
34///
35/// # Example
36/// ```
37/// let mut root = cursive::default();
38/// root.set_theme(better_theme());
39/// ```
40pub fn better_theme() -> Theme {
41    let mut theme_def = Theme {
42        shadow: false,
43        .. Theme::default()
44    };
45    theme_def.palette[PaletteColor::Background] = Color::TerminalDefault;
46    theme_def.palette[PaletteColor::Primary] = Color::Light(BaseColor::White);
47    theme_def.palette[PaletteColor::View] = Color::TerminalDefault;
48    theme_def.palette[PaletteColor::Highlight] = Color::Light(BaseColor::Blue);
49    theme_def.palette[PaletteColor::HighlightText] = Color::Dark(BaseColor::White);
50    theme_def.palette[PaletteColor::Secondary] = Color::Dark(BaseColor::Blue);
51    theme_def.palette[PaletteColor::TitlePrimary] = Color::Light(BaseColor::Blue);
52    theme_def.palette[PaletteColor::TitleSecondary] = Color::Dark(BaseColor::Blue);
53    theme_def.borders = BorderStyle::Outset;
54    theme_def
55}
56
57/// Convenience function that creates a dialog that runs a callback if the
58/// user selects "Yes"
59///
60/// # Example
61/// ```
62/// let mut root = cursive::default();
63/// root.add_layer(confirm_dialog("Are you sure?", "I solemnly swear that I am up to no good. /s", |view| view.quit()));
64/// root.run();
65/// ```
66pub fn confirm_dialog<T, U, C>(title: T, text: U, cb: C) -> OnEventView<Dialog>
67where
68    T: Display,
69    U: Into<StyledString>,
70    C: Fn(&mut Cursive) + Send + Sync + 'static
71{
72    Dialog::text(text)
73        .dismiss_button("No")
74        .button("Yes", cb)
75        .title(title.to_string())
76        .wrap_with(OnEventView::new)
77        .on_event(Event::Key(Key::Esc), |r| {
78            if r.screen().len() <= 1 { r.quit(); }
79            else { r.pop_layer(); }
80        })
81}
82
83/// Convenience function that shows a user a dialog box
84/// with a message that includes a back button
85///
86/// # Example
87/// ```
88/// let mut root = cursive::default();
89/// root.add_layer(info_dialog("Info", "This is important!"));
90/// root.run();
91/// ```
92pub fn info_dialog<T: Display, U: Into<StyledString>>(title: T, text: U) -> OnEventView<Dialog> {
93    Dialog::text(text)
94        .dismiss_button("Back")
95        .title(title.to_string())
96        .wrap_with(OnEventView::new)
97        .on_event(Event::Key(Key::Esc), |r| {
98            if r.screen().len() <= 1 { r.quit(); }
99            else { r.pop_layer(); }
100        })
101}
102
103/// Convenience function that creates a named `EditView` that has a better
104/// looking style and can optionally act as a password entry box
105///
106/// # Example
107/// ```
108/// let mut root = cursive::default();
109/// root.add_fullscreen_layer(
110///     Dialog::around(styled_editview("yes", "edit", false))
111///         .button("Quit", Cursive::quit)
112///         .title("Styled EditView Example")
113/// );
114/// root.run();
115/// ```
116pub fn styled_editview<C: Display>(content: C, view_name: &str, password: bool) -> NamedView<EditView> {
117    styled_editview_color(content, view_name, password, PaletteColor::TitlePrimary)
118}
119
120/// Same as `styled_editview()` but allows for a color to be chosen instead of using the highlight color
121pub fn styled_editview_color<T: Display, C: Into<ColorType>>(content: T, view_name: &str, password: bool, color: C) -> NamedView<EditView> {
122    let input_style = ColorStyle::new(
123        Color::Light(BaseColor::White),
124        color
125    );
126    let view = EditView::new().content(content.to_string()).style(input_style).filler(" ");
127
128    if password { view.secret() }
129    else { view }
130        .with_name(view_name)
131}
132
133/// Convenience function that return the state of a named check box
134///
135/// Returns false if the checkbox is not checked or the checkbox with
136/// the specified name does not exist
137pub fn get_checkbox_option(root: &mut Cursive, name: &str) -> bool {
138    if let Some(cbox) = root.find_name::<Checkbox>(name) {
139        cbox.is_checked()
140    }
141    else { false }
142}
143
144/// Convenience function that returns a horizontal `LinearLayout` that is a named check box with a label
145pub fn labeled_checkbox(text: &str, name: &str, checked: bool) -> LinearLayout {
146    labeled_checkbox_cb(text, name, checked, |_, _| { })
147}
148
149/// Same as `labeled_checkbox()` but also accepts a closure to execute when the check box's state changes
150pub fn labeled_checkbox_cb<C: Fn(&mut Cursive, bool) + Send + Sync + 'static>(text: &str, name: &str, checked: bool, callback: C) -> LinearLayout {
151    hlayout!(
152        Checkbox::new()
153            .with_checked(checked)
154            .on_change(callback)
155            .with_name(name),
156        TextView::new(format!(" {text}"))
157    )
158}
159
160/// Convenience function that shows a loading pop up
161///
162/// # Example
163/// ```
164/// let mut root = cursive::default();
165/// root.set_theme(better_theme());
166/// load_resource(&mut root,
167///     "Loading...", "Loading Dummy Number...",
168///     || {
169///         thread::sleep(Duration::from_secs(5));
170///         2 + 4
171///     },
172///     |root, val| {
173///         assert_eq!(val, 6);
174///         root.quit()
175///     }
176/// );
177///
178/// root.run();
179/// ```
180pub fn load_resource<T, D, M, R, F>(root: &mut Cursive, title: D, msg: M, task: R, finish_task: F)
181where
182    T: Send + Sync + 'static,
183    D: Display,
184    M: Into<StyledString>,
185    R: FnOnce() -> T + Send + Sync + 'static,
186    F: FnOnce(&mut Cursive, T) + Send + Sync + 'static,
187{
188    let loader = LoadingAnimation::new(msg, move || (task(), finish_task));
189    if root.fps().is_none() { root.set_fps(30); }
190    root.add_layer(
191        Dialog::around(loader.with_name("load")).title(title.to_string())
192            .wrap_with(OnEventView::new)
193            .on_event(Event::Refresh, |root: &mut Cursive| {
194                let mut loader = root.find_name::<LoadingAnimation<(T, F)>>("load").unwrap();
195                if loader.is_done() {
196                    root.pop_layer();
197                    let (val, finish_func) = loader.finish().unwrap();
198                    finish_func(root, val);
199                }
200            })
201    );
202}
203
204/// Horizontal spacer with fixed width
205pub fn fixed_hspacer(width: usize) -> Spacer { ResizedView::with_fixed_width(width, DummyView) }
206
207/// Horizontal spacer fills all the available width
208pub fn filled_hspacer() -> Spacer { ResizedView::with_full_width(DummyView) }
209
210/// Vertical spacer with fixed height
211pub fn fixed_vspacer(height: usize) -> Spacer { ResizedView::with_fixed_height(height, DummyView) }
212
213/// Vertical spacer fills all the available height
214pub fn filled_vspacer() -> Spacer { ResizedView::with_full_height(DummyView) }