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
use std::cmp::{min, max};
use ncurses::chtype;
use theme::ColorPair;
use vec::Vec2;
use printer::Printer;
/// Provide scrolling functionalities to a view.
pub struct ScrollBase {
pub start_line: usize,
pub content_height: usize,
pub view_height: usize,
}
impl ScrollBase {
pub fn new() -> Self {
ScrollBase {
start_line: 0,
content_height: 0,
view_height: 0,
}
}
/// Call this method whem the content or the view changes.
pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
self.view_height = view_height;
self.content_height = content_height;
if self.scrollable() {
self.start_line = min(self.start_line, self.content_height - self.view_height);
} else {
self.start_line = 0;
}
}
/// Returns `TRUE` if the view needs to scroll.
pub fn scrollable(&self) -> bool {
self.view_height < self.content_height
}
/// Returns `TRUE` unless we are at the top.
pub fn can_scroll_up(&self) -> bool {
self.start_line > 0
}
/// Returns `TRUE` unless we are at the bottom.
pub fn can_scroll_down(&self) -> bool {
self.start_line + self.view_height < self.content_height
}
/// Scroll to the top of the view.
pub fn scroll_top(&mut self) {
self.start_line = 0;
}
/// Makes sure that the given line is visible, scrolling if needed.
pub fn scroll_to(&mut self, y: usize) {
if y >= self.start_line + self.view_height {
self.start_line = 1 + y - self.view_height;
} else if y < self.start_line {
self.start_line = y;
}
}
/// Scroll to the bottom of the view.
pub fn scroll_bottom(&mut self) {
self.start_line = self.content_height - self.view_height;
}
/// Scroll down by the given number of line, never going further than the bottom of the view.
pub fn scroll_down(&mut self, n: usize) {
self.start_line = min(self.start_line + n, self.content_height - self.view_height);
}
/// Scroll up by the given number of lines, never going above the top of the view.
pub fn scroll_up(&mut self, n: usize) {
self.start_line -= min(self.start_line, n);
}
/// Draws the scroll bar and the content using the given drawer.
///
/// `line_drawer` will be called once for each line that needs to be drawn.
/// It will be given the absolute ID of the item to draw..
/// It will also be given a printer with the correct offset,
/// so it should only print on the first line.
///
/// # Examples
///
/// ```
/// # use cursive::view::ScrollBase;
/// # use cursive::printer::Printer;
/// # use cursive::theme;
/// # let scrollbase = ScrollBase::new();
/// # let printer = Printer::new((5,1), theme::load_default());
/// # let printer = &printer;
/// let lines = ["Line 1", "Line number 2"];
/// scrollbase.draw(printer, |printer, i| {
/// printer.print((0,0), lines[i]);
/// });
/// ```
pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
where F: Fn(&Printer, usize)
{
// Print the content in a sub_printer
let max_y = min(self.view_height, self.content_height - self.start_line);
let w = if self.scrollable() {
printer.size.x - 2
} else {
printer.size.x
};
for y in 0..max_y {
// Y is the actual coordinate of the line.
// The item ID is then Y + self.start_line
line_drawer(&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
y + self.start_line);
}
// And draw the scrollbar if needed
if self.view_height < self.content_height {
// We directly compute the size of the scrollbar (this allow use to avoid using floats).
// (ratio) * max_height
// Where ratio is ({start or end} / content.height)
let height = max(1, self.view_height * self.view_height / self.content_height);
// Number of different possible positions
let steps = self.view_height - height + 1;
// Now
let start = steps * self.start_line / (1 + self.content_height - self.view_height);
let color = if printer.focused {
ColorPair::Highlight
} else {
ColorPair::HighlightInactive
};
printer.print_vline((printer.size.x - 1, 0), printer.size.y, '|' as chtype);
printer.with_color(color, |printer| {
printer.print_vline((printer.size.x - 1, start), height, ' ' as chtype);
});
}
}
}