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
//! A module containing the in-built widgets and types required by them

use std::io;

use textwrap::{core::Fragment, WordSeparator};

use crate::{backend::Backend, events::KeyEvent, layout::Layout};

pub use crate::char_input::CharInput;
pub use crate::prompt::{Delimiter, Prompt};
pub use crate::select::{List, Select};
pub use crate::string_input::StringInput;
pub use crate::text::Text;

/// The default type for `filter_map` in [`StringInput`] and [`CharInput`]
pub type FilterMapChar = fn(char) -> Option<char>;

/// Character filter that lets every character through
pub(crate) fn no_filter(c: char) -> Option<char> {
    Some(c)
}

/// A trait to represent renderable objects.
///
/// There are 2 purposes of a widget.
/// 1. Rendering to the screen.
/// 2. Handling input events.
///
/// # Render Cycle
///
/// Rendering happens in a 3 step process.
/// 1. First, the height is calculated with the [`height`] function.
/// 2. Then, the [`render`] function is called which is where the actual drawing happens. The
///    cursor should end at the position reflected by the layout.
/// 3. Finally, the cursor position which the user needs should see is calculated with the
///    [`cursor_pos`] function.
///
/// While it is not a guarantee that the terminal will be in raw mode, it is highly recommended that
/// those implementing the render cycle call render while in raw mode.
///
/// [`height`]: Widget::height
/// [`render`]: Widget::render
/// [`cursor_pos`]: Widget::cursor_pos
pub trait Widget {
    /// Render to a given backend.
    ///
    /// The widget is responsible for updating the layout to reflect the space that it has used.
    fn render<B: Backend>(&mut self, layout: &mut Layout, backend: &mut B) -> io::Result<()>;

    /// The number of rows of the terminal the widget will take when rendered.
    ///
    /// The widget is responsible for updating the layout to reflect the space that it will use.
    fn height(&mut self, layout: &mut Layout) -> u16;

    /// The position of the cursor to be placed at after render. The returned value should be in the
    /// form of (x, y), with (0, 0) being the top left of the screen.
    ///
    /// For example, if you want the cursor to be at the first character that could be printed,
    /// `cursor_pos` would be `(layout.offset_x + layout.line_offset, layout.offset_y)`. Also see
    /// [`Layout::offset_cursor`].
    fn cursor_pos(&mut self, layout: Layout) -> (u16, u16);

    /// Handle a key input. It should return whether key was handled.
    fn handle_key(&mut self, key: KeyEvent) -> bool;
}

impl<T: std::ops::Deref<Target = str> + ?Sized> Widget for T {
    /// Does not allow multi-line strings. If the string requires more than a single line, it adds
    /// cuts it short and adds '...' to the end.
    ///
    /// If a multi-line string is required, use the [`Text`](crate::widgets::Text) widget.
    fn render<B: Backend>(&mut self, layout: &mut Layout, backend: &mut B) -> io::Result<()> {
        let max_width = layout.line_width() as usize;

        layout.offset_y += 1;
        layout.line_offset = 0;

        if max_width <= 3 {
            for _ in 0..max_width {
                backend.write_all(b".")?;
            }
        } else if textwrap::core::display_width(self) > max_width {
            let mut width = 0;
            let mut prev_whitespace_len = 0;
            let max_width = max_width - 3; // leave space for the '...'

            for word in WordSeparator::UnicodeBreakProperties.find_words(self) {
                width += word.width() as usize + prev_whitespace_len;
                if width > max_width {
                    break;
                }

                // Write out the whitespace only if the next word can also fit
                for _ in 0..prev_whitespace_len {
                    backend.write_all(b" ")?;
                }
                backend.write_all(word.as_bytes())?;

                prev_whitespace_len = word.whitespace_width() as usize;
            }

            backend.write_all(b"...")?;
        } else {
            backend.write_all(self.as_bytes())?;
        }

        backend
            .move_cursor_to(layout.offset_x, layout.offset_y)
            .map_err(Into::into)
    }

    /// Does not allow multi-line strings.
    ///
    /// If a multi-line string is required, use the [`Text`](crate::widgets::Text) widget.
    fn height(&mut self, layout: &mut Layout) -> u16 {
        layout.offset_y += 1;
        layout.line_offset = 0;
        1
    }

    /// Returns the location of the first character
    fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {
        layout.offset_cursor((layout.line_offset, 0))
    }

    /// This widget does not handle any events
    fn handle_key(&mut self, _: crate::events::KeyEvent) -> bool {
        false
    }
}