git_branchless_undo/tui/
cursive.rs

1//! Utilities to render an interactive text-based user interface.
2
3use cursive::backends::crossterm;
4use cursive_buffered_backend::BufferedBackend;
5use cursive_core::theme::{Color, PaletteColor};
6use cursive_core::{Cursive, CursiveRunner};
7
8use lib::core::effects::Effects;
9
10/// Create an instance of a `CursiveRunner`, and clean it up afterward.
11pub fn with_siv<T, F: FnOnce(Effects, CursiveRunner<Cursive>) -> eyre::Result<T>>(
12    effects: &Effects,
13    f: F,
14) -> eyre::Result<T> {
15    // Use crossterm to ensure that we support Windows.
16    let backend = crossterm::Backend::init()?;
17    let backend = BufferedBackend::new(backend);
18
19    let effects = effects.enable_tui_mode();
20    let mut siv = Cursive::new().into_runner(Box::new(backend));
21    siv.update_theme(|theme| {
22        theme.shadow = false;
23        theme.palette.extend(vec![
24            (PaletteColor::Background, Color::TerminalDefault),
25            (PaletteColor::View, Color::TerminalDefault),
26            (PaletteColor::Primary, Color::TerminalDefault),
27            (PaletteColor::TitlePrimary, Color::TerminalDefault),
28            (PaletteColor::TitleSecondary, Color::TerminalDefault),
29        ]);
30    });
31    f(effects, siv)
32}
33
34/// Type-safe "singleton" view: a kind of view which is addressed by name, for
35/// which exactly one copy exists in the Cursive application.
36pub trait SingletonView<V> {
37    /// Look up the instance of the singleton view in the application. Panics if
38    /// it hasn't been added.
39    fn find(siv: &mut Cursive) -> cursive_core::views::ViewRef<V>;
40}
41
42/// Create a set of views with unique names.
43///
44/// ```
45/// # use cursive_core::Cursive;
46/// # use cursive_core::views::{EditView, TextView};
47/// # use git_branchless_undo::declare_views;
48/// # use git_branchless_undo::tui::SingletonView;
49/// # fn main() {
50/// declare_views! {
51///     SomeDisplayView => TextView,
52///     SomeDataEntryView => EditView,
53/// }
54/// let mut siv = Cursive::new();
55/// siv.add_layer::<SomeDisplayView>(TextView::new("Hello, world!").into());
56/// assert_eq!(SomeDisplayView::find(&mut siv).get_content().source(), "Hello, world!");
57/// # }
58/// ```
59#[macro_export]
60macro_rules! declare_views {
61    { $( $k:ident => $v:ty ),* $(,)? } => {
62        $(
63            struct $k {
64                view: cursive_core::views::NamedView<$v>,
65            }
66
67            impl $crate::tui::SingletonView<$v> for $k {
68                fn find(siv: &mut Cursive) -> cursive_core::views::ViewRef<$v> {
69                    siv.find_name::<$v>(stringify!($k)).unwrap()
70                }
71            }
72
73            impl From<$v> for $k {
74                fn from(view: $v) -> Self {
75                    use cursive_core::view::Nameable;
76                    let view = view.with_name(stringify!($k));
77                    $k { view }
78                }
79            }
80
81            impl cursive_core::view::ViewWrapper for $k {
82                cursive_core::wrap_impl!(self.view: cursive_core::views::NamedView<$v>);
83            }
84        )*
85    };
86}