on master help sign in
ui: Trigger recalculate_size per popup render so contents can readjust
32977edBlaž Hrastnik
helix-term/src/ui/menu.rs+43−33
@@ -33,6 +33,8 @@ pub struct Menu<T: Item> {
33
33
34 scroll: usize,
34 scroll: usize,
35 size: (u16, u16),
35 size: (u16, u16),
36+ viewport: (u16, u16),
37+ recalculate: bool,
36}
38}
37
39
38impl<T: Item> Menu<T> {
40impl<T: Item> Menu<T> {
@@ -51,6 +53,8 @@ impl<T: Item> Menu<T> {
51 callback_fn: Box::new(callback_fn),
53 callback_fn: Box::new(callback_fn),
52 scroll: 0,
54 scroll: 0,
53 size: (0, 0),
55 size: (0, 0),
56+ viewport: (0, 0),
57+ recalculate: true,
54 };
58 };
55
59
56 // TODO: scoring on empty input should just use a fastpath
60 // TODO: scoring on empty input should just use a fastpath
@@ -83,6 +87,7 @@ impl<T: Item> Menu<T> {
83 // reset cursor position
87 // reset cursor position
84 self.cursor = None;
88 self.cursor = None;
85 self.scroll = 0;
89 self.scroll = 0;
90+ self.recalculate = true;
86 }
91 }
87
92
88 pub fn move_up(&mut self) {
93 pub fn move_up(&mut self) {
@@ -99,6 +104,41 @@ impl<T: Item> Menu<T> {
99 self.adjust_scroll();
104 self.adjust_scroll();
100 }
105 }
101
106
107+ fn recalculate_size(&mut self, viewport: (u16, u16)) {
108+ let n = self
109+ .options
110+ .first()
111+ .map(|option| option.row().cells.len())
112+ .unwrap_or_default();
113+ let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
114+ let row = option.row();
115+ // maintain max for each column
116+ for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
117+ let width = cell.content.width();
118+ if width > *acc {
119+ *acc = width;
120+ }
121+ }
122+
123+ acc
124+ });
125+ let len = max_lens.iter().sum::<usize>() + n + 1; // +1: reserve some space for scrollbar
126+ let width = len.min(viewport.0 as usize);
127+
128+ self.widths = max_lens
129+ .into_iter()
130+ .map(|len| Constraint::Length(len as u16))
131+ .collect();
132+
133+ let height = self.matches.len().min(10).min(viewport.1 as usize);
134+
135+ self.size = (width as u16, height as u16);
136+
137+ // adjust scroll offsets if size changed
138+ self.adjust_scroll();
139+ self.recalculate = false;
140+ }
141+
102 fn adjust_scroll(&mut self) {
142 fn adjust_scroll(&mut self) {
103 let win_height = self.size.1 as usize;
143 let win_height = self.size.1 as usize;
104 if let Some(cursor) = self.cursor {
144 if let Some(cursor) = self.cursor {
@@ -221,43 +261,13 @@ impl<T: Item + 'static> Component for Menu<T> {
221 }
261 }
222
262
223 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
263 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
224− let n = self
264+ if viewport != self.viewport || self.recalculate {
225− .options
265+ self.recalculate_size(viewport);
226− .first()
266+ }
227− .map(|option| option.row().cells.len())
228− .unwrap_or_default();
229− let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
230− let row = option.row();
231− // maintain max for each column
232− for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
233− let width = cell.content.width();
234− if width > *acc {
235− *acc = width;
236− }
237− }
238−
239− acc
240− });
241− let len = max_lens.iter().sum::<usize>() + n + 1; // +1: reserve some space for scrollbar
242− let width = len.min(viewport.0 as usize);
243−
244− self.widths = max_lens
245− .into_iter()
246− .map(|len| Constraint::Length(len as u16))
247− .collect();
248−
249− let height = self.options.len().min(10).min(viewport.1 as usize);
250−
251− self.size = (width as u16, height as u16);
252−
253− // adjust scroll offsets if size changed
254− self.adjust_scroll();
255
267
256 Some(self.size)
268 Some(self.size)
257 }
269 }
258
270
259− // TODO: required size should re-trigger when we filter items so we can draw a smaller menu
260−
261 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
271 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
262 let theme = &cx.editor.theme;
272 let theme = &cx.editor.theme;
263 let style = theme
273 let style = theme
helix-term/src/ui/popup.rs+14−7
@@ -16,8 +16,6 @@ pub struct Popup<T: Component> {
16}
16}
17
17
18impl<T: Component> Popup<T> {
18impl<T: Component> Popup<T> {
19− // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
20− // rendering)
21 pub fn new(contents: T) -> Self {
19 pub fn new(contents: T) -> Self {
22 Self {
20 Self {
23 contents,
21 contents,
@@ -38,20 +36,26 @@ impl<T: Component> Popup<T> {
38
36
39 let (width, height) = self.size;
37 let (width, height) = self.size;
40
38
39+ // if there's a orientation preference, use that
40+ // if we're on the top part of the screen, do below
41+ // if we're on the bottom part, do above
42+
41 // -- make sure frame doesn't stick out of bounds
43 // -- make sure frame doesn't stick out of bounds
42 let mut rel_x = position.col as u16;
44 let mut rel_x = position.col as u16;
43− let rel_y = position.row as u16;
45+ let mut rel_y = position.row as u16;
44 if viewport.width <= rel_x + width {
46 if viewport.width <= rel_x + width {
45 rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
47 rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
46− };
48+ }
47
49
48 // TODO: be able to specify orientation preference. We want above for most popups, below
50 // TODO: be able to specify orientation preference. We want above for most popups, below
49 // for menus/autocomplete.
51 // for menus/autocomplete.
50− if height <= rel_y {
52+ if viewport.height > rel_y + height {
51− (rel_x, rel_y.saturating_sub(height)) // position above point
53+ rel_y += 1 // position below point
52 } else {
54 } else {
53− (rel_x, rel_y + 1) // position below point
55+ rel_y = rel_y.saturating_sub(height) // position above point
54 }
56 }
57+
58+ (rel_x, rel_y)
55 }
59 }
56
60
57 pub fn get_size(&self) -> (u16, u16) {
61 pub fn get_size(&self) -> (u16, u16) {
@@ -133,6 +137,9 @@ impl<T: Component> Component for Popup<T> {
133 }
137 }
134
138
135 fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
139 fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
140+ // trigger required_size so we recalculate if the child changed
141+ self.required_size((viewport.width, viewport.height));
142+
136 cx.scroll = Some(self.scroll);
143 cx.scroll = Some(self.scroll);
137
144
138 let (rel_x, rel_y) = self.get_rel_position(viewport, cx);
145 let (rel_x, rel_y) = self.get_rel_position(viewport, cx);
helix-term/src/ui/text.rs+15−5
@@ -5,11 +5,17 @@ use helix_view::graphics::Rect;
5
5
6pub struct Text {
6pub struct Text {
7 contents: String,
7 contents: String,
8+ size: (u16, u16),
9+ viewport: (u16, u16),
8}
10}
9
11
10impl Text {
12impl Text {
11 pub fn new(contents: String) -> Self {
13 pub fn new(contents: String) -> Self {
14+ Self {
12− Self { contents }
15+ contents,
16+ size: (0, 0),
17+ viewport: (0, 0),
18+ }
13 }
19 }
14}
20}
15impl Component for Text {
21impl Component for Text {
@@ -24,9 +30,13 @@ impl Component for Text {
24 }
30 }
25
31
26 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
32 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
33+ if viewport != self.viewport {
27− let contents = tui::text::Text::from(self.contents.clone());
34+ let contents = tui::text::Text::from(self.contents.clone());
28− let width = std::cmp::min(contents.width() as u16, viewport.0);
35+ let width = std::cmp::min(contents.width() as u16, viewport.0);
29− let height = std::cmp::min(contents.height() as u16, viewport.1);
36+ let height = std::cmp::min(contents.height() as u16, viewport.1);
30− Some((width, height))
37+ self.size = (width, height);
38+ self.viewport = viewport;
39+ }
40+ Some(self.size)
31 }
41 }
32}
42}