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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//!
//! A message dialog.
//!

use crate::_private::NonExhaustive;
use crate::button::{Button, ButtonOutcome, ButtonState, ButtonStyle};
use crate::event::Outcome;
use crate::layout_dialog::layout_dialog;
use rat_event::{ct_event, FocusKeys, HandleEvent};
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Flex, Margin, Rect};
use ratatui::prelude::{StatefulWidget, Style};
use ratatui::text::{Line, Text};
use ratatui::widgets::{Block, Paragraph, Widget};
use std::fmt::Debug;

/// Basic status dialog for longer messages.
#[derive(Debug, Default)]
pub struct MsgDialog {
    style: Style,
    button_style: ButtonStyle,
}

/// Combined style.
#[derive(Debug)]
pub struct MsgDialogStyle {
    pub style: Style,
    pub button: ButtonStyle,
    pub non_exhaustive: NonExhaustive,
}

/// State for the status dialog.
#[derive(Debug)]
pub struct MsgDialogState {
    // Dialog is active.
    pub active: bool,
    pub area: Rect,
    pub message: String,
    pub button: ButtonState,

    pub non_exhaustive: NonExhaustive,
}

impl MsgDialog {
    pub fn new() -> Self {
        Self {
            style: Default::default(),
            button_style: Default::default(),
        }
    }

    /// Combined style
    pub fn styles(mut self, styles: MsgDialogStyle) -> Self {
        self.style = styles.style;
        self.button_style = styles.button;
        self
    }

    /// Base style
    pub fn style(mut self, style: impl Into<Style>) -> Self {
        self.style = style.into();
        self
    }

    /// Button style.
    pub fn button_style(mut self, style: ButtonStyle) -> Self {
        self.button_style = style;
        self
    }
}

impl Default for MsgDialogStyle {
    fn default() -> Self {
        Self {
            style: Default::default(),
            button: Default::default(),
            non_exhaustive: NonExhaustive,
        }
    }
}

impl MsgDialogState {
    /// Clear
    pub fn clear(&mut self) {
        self.active = false;
        self.message = Default::default();
    }

    /// *Append* to the message.
    pub fn append(&mut self, msg: &str) {
        self.active = true;
        if !self.message.is_empty() {
            self.message.push('\n');
        }
        self.message.push_str(msg);
    }
}

impl Default for MsgDialogState {
    fn default() -> Self {
        let s = Self {
            active: false,
            area: Default::default(),
            message: Default::default(),
            button: Default::default(),
            non_exhaustive: NonExhaustive,
        };
        s
    }
}

impl StatefulWidget for MsgDialog {
    type State = MsgDialogState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        if state.active {
            let l_dlg = layout_dialog(
                area,
                Constraint::Percentage(61),
                Constraint::Percentage(61),
                Margin::new(1, 1),
                [Constraint::Length(10)],
                0,
                Flex::End,
            );

            state.area = l_dlg.area;

            //
            let block = Block::default().style(self.style);

            let mut lines = Vec::new();
            for t in state.message.split('\n') {
                lines.push(Line::from(t));
            }
            let text = Text::from(lines).alignment(Alignment::Center);
            let para = Paragraph::new(text);

            let ok = Button::from("Ok").styles(self.button_style).focused(true);

            for y in l_dlg.dialog.y..l_dlg.dialog.bottom() {
                let idx = buf.index_of(l_dlg.dialog.x, y);
                for x in 0..l_dlg.dialog.width as usize {
                    buf.content[idx + x].reset();
                    buf.content[idx + x].set_style(self.style);
                }
            }

            block.render(l_dlg.dialog, buf);
            para.render(l_dlg.area, buf);
            ok.render(l_dlg.buttons[0], buf, &mut state.button);
        }
    }
}

impl HandleEvent<crossterm::event::Event, FocusKeys, Outcome> for MsgDialogState {
    fn handle(&mut self, event: &crossterm::event::Event, _: FocusKeys) -> Outcome {
        if self.active {
            let r = match self.button.handle(event, FocusKeys) {
                ButtonOutcome::Pressed => {
                    self.clear();
                    self.active = false;
                    Outcome::Changed
                }
                v => v.into(),
            };
            let r = if r == Outcome::NotUsed {
                match event {
                    ct_event!(keycode press Esc) => {
                        self.clear();
                        self.active = false;
                        Outcome::Changed
                    }
                    _ => Outcome::NotUsed,
                }
            } else {
                r
            };
            r
        } else {
            Outcome::NotUsed
        }
    }
}