Skip to main content

git_branchless_undo/tui/
cursive.rs

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