1use {
2 crate::crossterm::terminal,
3 std::convert::{
4 TryFrom,
5 TryInto,
6 },
7};
8
9const DEFAULT_TERMINAL_WIDTH: u16 = 50;
11
12const DEFAULT_TERMINAL_HEIGHT: u16 = 20;
14
15#[derive(Debug, PartialEq, Eq, Clone)]
17pub struct Area {
18 pub left: u16,
19 pub top: u16,
20 pub width: u16,
21 pub height: u16,
22}
23
24impl Default for Area {
25 fn default() -> Self {
26 Self::uninitialized()
27 }
28}
29
30impl Area {
31 pub const fn uninitialized() -> Area {
34 Area {
35 left: 0,
36 top: 0,
37 height: 1,
38 width: 5,
39 }
40 }
41
42 pub const fn new(left: u16, top: u16, width: u16, height: u16) -> Area {
44 Area {
45 left,
46 top,
47 width,
48 height,
49 }
50 }
51
52 pub fn full_screen() -> Area {
54 let (width, height) = terminal_size();
55 Area {
56 left: 0,
57 top: 0,
58 width,
59 height,
60 }
61 }
62
63 pub const fn right(&self) -> u16 {
64 self.left + self.width
65 }
66
67 pub const fn bottom(&self) -> u16 {
68 self.top + self.height
69 }
70
71 pub const fn contains(&self, x: u16, y: u16) -> bool {
73 x >= self.left && x < self.left + self.width && y >= self.top && y < self.top + self.height
74 }
75
76 pub fn pad(&mut self, dx: u16, dy: u16) {
78 self.left += dx;
80 self.top += dy;
81 self.width -= 2 * dx;
82 self.height -= 2 * dy;
83 }
84
85 pub fn pad_for_max_width(&mut self, max_width: u16) {
87 if max_width >= self.width {
88 return;
89 }
90 let pw = self.width - max_width;
91 self.left += pw / 2;
92 self.width -= pw;
93 }
94
95 pub fn scrollbar<U>(
100 &self,
101 scroll: U, content_height: U,
103 ) -> Option<(u16, u16)>
104 where
105 U: Into<usize>,
106 {
107 compute_scrollbar(scroll, content_height, self.height, self.top)
108 }
109}
110
111pub fn compute_scrollbar<U1, U2, U3>(
118 scroll: U1, content_height: U1, available_height: U3, top: U2, ) -> Option<(U2, U2)>
123where
124 U1: Into<usize>, U2: Into<usize> + TryFrom<usize>, <U2 as TryFrom<usize>>::Error: std::fmt::Debug,
127 U3: Into<usize> + TryFrom<usize>, <U3 as TryFrom<usize>>::Error: std::fmt::Debug,
129{
130 let scroll: usize = scroll.into();
131 let content_height: usize = content_height.into();
132 let available_height: usize = available_height.into();
133 let top: usize = top.into();
134 if content_height <= available_height {
135 return None;
136 }
137 let mut track_before = scroll * available_height / content_height;
138 if track_before == 0 && scroll > 0 {
139 track_before = 1;
140 }
141 let thumb_height = available_height * available_height / content_height;
142 let scrollbar_top = top + track_before;
143 let mut scrollbar_bottom = scrollbar_top + thumb_height;
144 if scroll + available_height < content_height && available_height > 3 {
145 scrollbar_bottom = scrollbar_bottom
146 .min(top + available_height - 2)
147 .max(scrollbar_top);
148 }
149 let scrollbar_top = scrollbar_top.try_into().unwrap();
152 let scrollbar_bottom = scrollbar_bottom.try_into().unwrap();
153 Some((scrollbar_top, scrollbar_bottom))
154}
155
156#[must_use]
159pub fn terminal_size() -> (u16, u16) {
160 let size = terminal::size();
161 size.unwrap_or((DEFAULT_TERMINAL_WIDTH, DEFAULT_TERMINAL_HEIGHT))
162}