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
mod command;
mod label;
mod list;
mod text;

pub use command::*;
pub use label::*;
pub use list::*;
pub use text::*;
use tui::{
    backend::Backend,
    layout::Rect,
    widgets::{StatefulWidget, Widget},
    Frame,
};

use crate::theme::Theme;

// Represents an offset
#[derive(Default, Clone, Copy)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Offset {
    pub x: u16,
    pub y: u16,
}

impl Offset {
    pub fn new(x: u16, y: u16) -> Self {
        Self { x, y }
    }
}

// Represents an area
#[derive(Clone, Copy)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Area {
    pub width: u16,
    pub height: u16,
}
impl Default for Area {
    fn default() -> Self {
        Self { width: 1, height: 1 }
    }
}

impl Area {
    pub fn default_visible() -> Self {
        Self { width: 25, height: 2 }
    }

    pub fn new(width: u16, height: u16) -> Self {
        Self { width, height }
    }

    pub fn min_width(mut self, width: u16) -> Self {
        self.width = width.max(self.width);
        self
    }
}

/// Custom widget
pub trait CustomWidget<'s> {
    type Inner: Widget;

    /// Retrieves the minimum size needed to render this widget
    fn min_size(&self) -> Area;

    /// Determines if the widget is currently focused
    fn is_focused(&self) -> bool;

    /// Prepares both cursor offset (relative to the area) and widget parts
    fn prepare(&'s self, area: Rect, theme: Theme) -> (Option<Offset>, Self::Inner);

    /// Renders itself in the frame and places the cursor if needed
    fn render_in<B: Backend>(&'s self, frame: &mut Frame<B>, area: Rect, theme: Theme)
    where
        Self: Sized,
    {
        let (offset, widget) = self.prepare(area, theme);
        frame.render_widget(widget, area);

        if self.is_focused() {
            if let Some(offset) = offset {
                // Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering
                frame.set_cursor(area.x + offset.x, area.y + offset.y);
            }
        }
    }
}

pub trait CustomStatefulWidget<'s> {
    type Inner: StatefulWidget;

    /// Retrieves the minimum size needed to render this widget
    fn min_size(&self) -> Area;

    /// Prepares widget and state parts
    fn prepare(&'s mut self, area: Rect, theme: Theme)
    -> (Self::Inner, &'s mut <Self::Inner as StatefulWidget>::State);

    /// Renders itself in the frame
    fn render_in<B: Backend>(&'s mut self, frame: &mut Frame<B>, area: Rect, theme: Theme)
    where
        Self: Sized,
    {
        let (widget, state) = self.prepare(area, theme);
        frame.render_stateful_widget(widget, area, state);
    }
}

pub trait IntoWidget<W> {
    /// Converts self into a widget
    fn into_widget(self, theme: Theme) -> W;
}

impl<W, T> IntoWidget<W> for T
where
    T: Into<W>,
{
    fn into_widget(self, _theme: Theme) -> W {
        self.into()
    }
}

pub trait IntoCursorWidget<W> {
    /// Converts self into a widget and its cursor
    fn into_widget_and_cursor(self, theme: Theme) -> (W, Option<(Offset, Area)>);
}

impl<W, T> IntoCursorWidget<W> for T
where
    T: Into<W>,
{
    fn into_widget_and_cursor(self, _theme: Theme) -> (W, Option<(Offset, Area)>) {
        (self.into(), None)
    }
}