on master help sign in
ui: Trigger recalculate_size per popup render so contents can readjust
commit32977ed34124a99af7b51057a6723203ce23c59c authorBlaž Hrastnik <blaz@mxxn.io> dateThu 9 Sep 2021, 12:35 (5 years ago) parentf2c73d1
3 files changed+72−45
helix-term/src/ui/menu.rs+43−33 helix-term/src/ui/popup.rs+14−7 helix-term/src/ui/text.rs+15−5
helix-term/src/ui/menu.rs+43−33
@@ -33,6 +33,8 @@ pub struct Menu<T: Item> {
3333
3434 scroll: usize,
3535 size: (u16, u16),
36+ viewport: (u16, u16),
37+ recalculate: bool,
3638}
3739
3840impl<T: Item> Menu<T> {
@@ -51,6 +53,8 @@ impl<T: Item> Menu<T> {
5153 callback_fn: Box::new(callback_fn),
5254 scroll: 0,
5355 size: (0, 0),
56+ viewport: (0, 0),
57+ recalculate: true,
5458 };
5559
5660 // TODO: scoring on empty input should just use a fastpath
@@ -83,6 +87,7 @@ impl<T: Item> Menu<T> {
8387 // reset cursor position
8488 self.cursor = None;
8589 self.scroll = 0;
90+ self.recalculate = true;
8691 }
8792
8893 pub fn move_up(&mut self) {
@@ -99,6 +104,41 @@ impl<T: Item> Menu<T> {
99104 self.adjust_scroll();
100105 }
101106
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+
102142 fn adjust_scroll(&mut self) {
103143 let win_height = self.size.1 as usize;
104144 if let Some(cursor) = self.cursor {
@@ -221,43 +261,13 @@ impl<T: Item + 'static> Component for Menu<T> {
221261 }
222262
223263 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
224− let n = self
225− .options
226− .first()
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();
264+ if viewport != self.viewport || self.recalculate {
265+ self.recalculate_size(viewport);
266+ }
255267
256268 Some(self.size)
257269 }
258270
259− // TODO: required size should re-trigger when we filter items so we can draw a smaller menu
260−
261271 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
262272 let theme = &cx.editor.theme;
263273 let style = theme
helix-term/src/ui/popup.rs+14−7
@@ -16,8 +16,6 @@ pub struct Popup<T: Component> {
1616}
1717
1818impl<T: Component> Popup<T> {
19− // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
20− // rendering)
2119 pub fn new(contents: T) -> Self {
2220 Self {
2321 contents,
@@ -38,20 +36,26 @@ impl<T: Component> Popup<T> {
3836
3937 let (width, height) = self.size;
4038
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+
4143 // -- make sure frame doesn't stick out of bounds
4244 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;
4446 if viewport.width <= rel_x + width {
4547 rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
46− };
48+ }
4749
4850 // TODO: be able to specify orientation preference. We want above for most popups, below
4951 // for menus/autocomplete.
50− if height <= rel_y {
51− (rel_x, rel_y.saturating_sub(height)) // position above point
52+ if viewport.height > rel_y + height {
53+ rel_y += 1 // position below point
5254 } else {
53− (rel_x, rel_y + 1) // position below point
55+ rel_y = rel_y.saturating_sub(height) // position above point
5456 }
57+
58+ (rel_x, rel_y)
5559 }
5660
5761 pub fn get_size(&self) -> (u16, u16) {
@@ -133,6 +137,9 @@ impl<T: Component> Component for Popup<T> {
133137 }
134138
135139 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+
136143 cx.scroll = Some(self.scroll);
137144
138145 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;
55
66pub struct Text {
77 contents: String,
8+ size: (u16, u16),
9+ viewport: (u16, u16),
810}
911
1012impl Text {
1113 pub fn new(contents: String) -> Self {
12− Self { contents }
14+ Self {
15+ contents,
16+ size: (0, 0),
17+ viewport: (0, 0),
18+ }
1319 }
1420}
1521impl Component for Text {
@@ -24,9 +30,13 @@ impl Component for Text {
2430 }
2531
2632 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
27− let contents = tui::text::Text::from(self.contents.clone());
28− let width = std::cmp::min(contents.width() as u16, viewport.0);
29− let height = std::cmp::min(contents.height() as u16, viewport.1);
30− Some((width, height))
33+ if viewport != self.viewport {
34+ let contents = tui::text::Text::from(self.contents.clone());
35+ let width = std::cmp::min(contents.width() as u16, viewport.0);
36+ let height = std::cmp::min(contents.height() as u16, viewport.1);
37+ self.size = (width, height);
38+ self.viewport = viewport;
39+ }
40+ Some(self.size)
3141 }
3242}