fm/modes/utils/
content_window.rs

1use std::cmp::{max, min};
2
3/// Holds the information about the displayed window of lines.
4/// When there's too much lines to display in one screen, we can scroll
5/// and this struct is responsible for that.
6/// Scrolling is done with `scroll_to`, `scroll_up_one`, `scroll_down_one`
7/// methods.
8#[derive(Debug, Clone)]
9pub struct ContentWindow {
10    /// The index of the first displayed element.
11    pub top: usize,
12    /// The index of the last displayed element + 1.
13    pub bottom: usize,
14    /// The number of displayble elements.
15    pub len: usize,
16    /// The height of the rect containing the elements.
17    pub height: usize,
18}
19
20impl Default for ContentWindow {
21    fn default() -> Self {
22        Self::new(0, 80)
23    }
24}
25
26impl ContentWindow {
27    /// The padding around the last displayed filename
28    pub const WINDOW_PADDING: usize = 4;
29    pub const WINDOW_PADDING_FUZZY: u32 = 3;
30    /// The space of the top element as an u16 for convenience
31    pub const WINDOW_MARGIN_TOP_U16: u16 = 2;
32    /// The space for the bottom row
33    pub const WINDOW_MARGIN_BOTTOM: usize = 1;
34    /// How many rows are reserved for the header ?
35    pub const HEADER_ROWS: usize = 3;
36    /// How many rows are reserved for the footer ?
37    const FOOTER_ROWS: usize = 2;
38
39    /// How many rows could be displayed with given height ?
40    /// It's not the number of rows displayed since the content may
41    /// not be long enough to fill the window.
42    fn nb_displayed_rows(rect_height: usize) -> usize {
43        rect_height.saturating_sub(Self::HEADER_ROWS + Self::FOOTER_ROWS)
44    }
45
46    /// Default value for the bottom index.
47    /// minimum of terminal height minus reserved rows and the length of the content.
48    fn default_bottom(len: usize, used_height: usize) -> usize {
49        min(len, used_height)
50    }
51
52    /// Returns a new `ContentWindow` instance with values depending of
53    /// number of displayable elements and height of the terminal screen.
54    pub fn new(len: usize, rect_height: usize) -> Self {
55        let height = Self::nb_displayed_rows(rect_height);
56        let top = 0;
57        let bottom = Self::default_bottom(len, height);
58        ContentWindow {
59            top,
60            bottom,
61            len,
62            height,
63        }
64    }
65
66    /// Set the height of file window.
67    pub fn set_height(&mut self, terminal_height: usize) {
68        self.height = Self::nb_displayed_rows(terminal_height);
69        self.bottom = Self::default_bottom(self.len, self.height);
70    }
71
72    pub fn set_len(&mut self, len: usize) {
73        self.len = len;
74        self.bottom = Self::default_bottom(len, self.height);
75    }
76
77    /// Move the window one line up if possible.
78    /// Does nothing if the index can't be reached.
79    pub fn scroll_up_one(&mut self, index: usize) {
80        if (index < self.top + Self::WINDOW_PADDING || index > self.bottom) && self.top > 0 {
81            self.top -= 1;
82            self.bottom -= 1;
83        }
84        self.scroll_to(index)
85    }
86
87    /// Move the window one line down if possible.
88    /// Does nothing if the index can't be reached.
89    pub fn scroll_down_one(&mut self, index: usize) {
90        if self.len < self.height {
91            return;
92        }
93        if index < self.top || index + Self::WINDOW_PADDING > self.bottom {
94            self.top += 1;
95            self.bottom += 1;
96        }
97        self.scroll_to(index)
98    }
99
100    /// Scroll the window to this index if possible.
101    /// Does nothing if the index can't be reached.
102    pub fn scroll_to(&mut self, index: usize) {
103        if self.len < self.height {
104            return;
105        }
106        if self.is_index_outside_window(index) {
107            let height = max(self.bottom.saturating_sub(self.top), self.height);
108            self.top = index.saturating_sub(Self::WINDOW_PADDING);
109            self.bottom = self.top + height;
110        }
111    }
112
113    /// Reset the window to the first item of the content.
114    pub fn reset(&mut self, len: usize) {
115        self.len = len;
116        self.top = 0;
117        self.bottom = Self::default_bottom(self.len, self.height);
118    }
119
120    /// True iff the index is outside the displayed window or
121    /// too close from the border.
122    /// User shouldn't be able to reach the last elements
123    fn is_index_outside_window(&self, index: usize) -> bool {
124        index < self.top || index >= self.bottom
125    }
126
127    pub fn is_row_in_header(row: u16) -> bool {
128        row < Self::HEADER_ROWS as u16
129    }
130
131    pub fn preview_page_up(&mut self, skip: usize) {
132        if self.top == 0 {
133            return;
134        }
135        let skip = min(self.top, skip);
136        self.bottom -= skip;
137        self.top -= skip;
138    }
139
140    pub fn preview_page_down(&mut self, skip: usize, preview_len: usize) {
141        if self.bottom < preview_len {
142            let skip = min(preview_len - self.bottom, skip);
143            self.bottom += skip;
144            self.top += skip;
145        }
146    }
147}