1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! A module containing the `Modal` component and related hooks.
//!
//! The [`Modal`] component allows a user to present a [`Component`] on top of
//! the children of the modal, from anywhere inside it. This is useful for popups such
//! as input boxes, or error messages. See the [`Modal`] structure ocumentation for details.
//!
//! [`Modal`]: struct.Modal.html

mod hook;

pub use self::hook::{use_modal, Funcs};
use crate::{
  components::{children::Children, Component},
  element::{Any as AnyElement, Element},
  event::{KeyEvent, KeyHandler},
  state::use_state,
  terminal::{Frame, Rect},
};

/// A component supporting modal-style overlays.
///
/// The [`Modal`] component allows a user to present a [`Component`] on top of
/// the children of the modal, from anywhere inside it. This is useful for popups such
/// as input boxes, or error messages.
///
/// For example, if we wanted to show an error message
/// whenever the `Enter` key is pressed, we can do something like this:
/// ```rust
/// # use intuitive::{component, components::{Centered, Section, Text, experimental::modal::{use_modal, Modal}}, style::Color, render, on_key};
/// #
/// #[component(MyComponent)]
/// fn render() {
///   let modal = use_modal();
///
///   let on_key = on_key! {
///     KeyEvent { code: Enter, .. } => modal.show(render! {
///       Centered() {
///         Section(title: "Error", border: Color::Red) {
///           Text(text: "Enter was pressed!")
///         }
///       }
///     }),
///
///     KeyEvent { code: Esc, .. } if modal.is_shown() => modal.hide(),
///     KeyEvent { code: Esc, .. } => event::quit(),
///   };
///
///   render! {
///     Section(title: "Some Example UI", on_key)
///   }
/// }
///
/// #[component(Root)]
/// fn render() {
///   render! {
///     Modal() {
///       MyComponent()
///     }
///   }
/// }
///
/// ```
/// In order to overlay an error message on top of `MyComponent`, we render it
/// as a child of a [`Modal`]. Then, in any descendant of this [`Modal`], we can call
/// [`use_modal`] to mutate the state of that [`Modal`].
///
/// # Internals
/// The [`Modal`] is somewhat special in that it does not (yet) use the built-in
/// [`use_state`] hooks, but instead has its own internal `static` hook manager.
///
/// [`Modal`]: struct.Modal.html
/// [`Component`]: trait.Component.html
/// [`use_state`]: ../../state/fn.use_state.html
/// [`use_modal`]: fn.use_modal.html
#[derive(Default)]
pub struct Modal {
  pub children: Children<1>,
  pub on_key: KeyHandler,
}

impl Component for Modal {
  fn render(&self) -> AnyElement {
    let modal = use_state(|| None);
    let funcs = use_state(|| Funcs::new(modal.clone()));

    hook::set_modal_funcs(funcs.get());

    AnyElement::new(Frozen {
      modal: modal.get().map(|modal| modal.render()),

      content: self.children[0].render(),
      on_key: self.on_key.clone(),
    })
  }
}

struct Frozen {
  modal: Option<AnyElement>,

  content: AnyElement,
  on_key: KeyHandler,
}

impl Element for Frozen {
  fn on_key(&self, event: KeyEvent) {
    self.on_key.handle_or(event, |event| self.content.on_key(event));
  }

  fn draw(&self, rect: Rect, frame: &mut Frame) {
    self.content.draw(rect, frame);

    if let Some(modal) = &self.modal {
      modal.draw(rect, frame);
    }
  }
}