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
use std::io;
use crossterm::{self, TerminalCursor, Terminal, ClearType};

use crate::area::Area;
use crate::displayable_line::DisplayableLine;
use crate::text::FmtText;

/// a scrollable text, in a specific area.
/// The text is assumed to have been computed for the given area
/// (it's generally recommended to use a MadView instead of a
/// TextView to ensure the text is properly computed).
pub struct TextView<'a, 't> {
    area: &'a Area,
    text: &'t FmtText<'t, 't>,
    pub scroll: i32, // 0 for no scroll, positive if scrolled
    pub show_scrollbar: bool,
}

impl<'a, 't> TextView<'a, 't> {

    /// make a displayed text, that is a text in an area
    pub fn from(
        area: &'a Area,
        text: &'t FmtText<'_, '_>,
    ) -> TextView<'a, 't> {
        TextView {
            area,
            text,
            scroll: 0,
            show_scrollbar: true,
        }
    }

    #[inline(always)]
    pub fn content_height(&self) -> i32 {
        self.text.lines.len() as i32
    }

    /// return an option which when filled contains
    ///  a tupple with the top and bottom of the vertical
    ///  scrollbar. Return none when the content fits
    ///  the available space (or if show_scrollbar is false).
    #[inline(always)]
    pub fn scrollbar(&self) -> Option<(u16, u16)> {
        if self.show_scrollbar {
            self.area.scrollbar(self.scroll, self.content_height())
        } else {
            None
        }
    }

    /// display the text in the area, taking the scroll into account.
    pub fn write(&self) -> io::Result<()> {
        let terminal = Terminal::new();
        let cursor = TerminalCursor::new();
        let scrollbar = self.scrollbar();
        let sx = self.area.left + self.area.width;
        let mut i = self.scroll as usize;
        for y in 0..self.area.height {
            cursor.goto(self.area.left, self.area.top+y)?;
            if i < self.text.lines.len() {
                let dl = DisplayableLine::new(
                    self.text.skin,
                    &self.text.lines[i],
                    self.text.width,
                );
                print!("{}", &dl);
                i += 1;
            } else {
                terminal.clear(ClearType::UntilNewLine)?;
            }
            if let Some((sctop, scbottom)) = scrollbar {
                cursor.goto(sx, self.area.top+y)?;
                if sctop <= y && y <= scbottom {
                    print!("{}", self.text.skin.scrollbar.thumb);
                } else {
                    print!("{}", self.text.skin.scrollbar.track);
                }
            }
        }
        Ok(())
    }

    /// set the scroll amount.
    /// lines_count can be negative
    pub fn try_scroll_lines(&mut self, lines_count: i32) {
        self.scroll = (self.scroll + lines_count)
            .min(self.content_height() - (self.area.height as i32) + 1)
            .max(0);

    }
    /// set the scroll amount.
    /// lines_count can be negative
    pub fn try_scroll_pages(&mut self, pages_count: i32) {
        self.try_scroll_lines(pages_count * self.area.height as i32);
    }
}