summaryrefslogtreecommitdiffstats
path: root/src/ui/widgets/tui_text.rs
blob: 1a1064c7ac14339bd5ef208bc06c4b081d61d7ff (plain)
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
use tui::buffer::Buffer;
use tui::layout::Rect;
use tui::style::{Color, Style};
use tui::widgets::Widget;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

#[derive(Clone, Debug)]
pub struct LineInfo {
    pub start: usize,
    pub end: usize,
    pub width: usize,
}

pub struct TuiMultilineText<'a> {
    _s: &'a str,
    _width: usize,
    _lines: Vec<LineInfo>,
    _style: Style,
}

impl<'a> TuiMultilineText<'a> {
    pub fn new(s: &'a str, area_width: usize) -> Self {
        // TODO: This is a very hacky way of doing it and I would like
        // to clean this up more

        let default_style = Style::default().fg(Color::Reset).bg(Color::Reset);

        let s_width = s.width();
        if s_width < area_width {
            return Self {
                _s: s,
                _lines: vec![LineInfo {
                    start: 0,
                    end: s.len(),
                    width: s_width,
                }],
                _width: area_width,
                _style: default_style,
            };
        }

        let filter = |(i, c): (usize, char)| {
            let w = c.width()?;
            Some((i, c, w))
        };

        let mut lines = Vec::with_capacity(s.len() / area_width);

        let mut start = 0;
        let mut line_width = 0;
        for (i, _, w) in s.char_indices().filter_map(filter) {
            if line_width + w < area_width {
                line_width += w;
                continue;
            }
            lines.push(LineInfo {
                start,
                end: i,
                width: line_width,
            });
            line_width = w;
            start = i;
        }
        lines.push(LineInfo {
            start,
            end: s.len(),
            width: s[start..s.len()].width(),
        });

        Self {
            _s: s,
            _lines: lines,
            _width: area_width,
            _style: default_style,
        }
    }

    pub fn width(&self) -> usize {
        self._width
    }

    pub fn height(&self) -> usize {
        if self._lines[self._lines.len() - 1].width >= self.width() {
            return self.len() + 1;
        }
        self.len()
    }
    pub fn len(&self) -> usize {
        self._lines.len()
    }

    pub fn iter(&self) -> impl Iterator<Item = &LineInfo> {
        self._lines.iter()
    }
}

impl<'a> Widget for TuiMultilineText<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let area_left = area.left();
        let area_top = area.top();
        for (i, line_info) in self.iter().enumerate() {
            buf.set_string(
                area_left,
                area_top + i as u16,
                &self._s[line_info.start..line_info.end],
                self._style,
            );
        }
    }
}