diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 24dd3e61..dab0c34f 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -33,6 +33,8 @@ pub struct Menu { scroll: usize, size: (u16, u16), + viewport: (u16, u16), + recalculate: bool, } impl Menu { @@ -51,6 +53,8 @@ impl Menu { callback_fn: Box::new(callback_fn), scroll: 0, size: (0, 0), + viewport: (0, 0), + recalculate: true, }; // TODO: scoring on empty input should just use a fastpath @@ -83,6 +87,7 @@ impl Menu { // reset cursor position self.cursor = None; self.scroll = 0; + self.recalculate = true; } pub fn move_up(&mut self) { @@ -99,6 +104,41 @@ impl Menu { self.adjust_scroll(); } + fn recalculate_size(&mut self, viewport: (u16, u16)) { + let n = self + .options + .first() + .map(|option| option.row().cells.len()) + .unwrap_or_default(); + let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| { + let row = option.row(); + // maintain max for each column + for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { + let width = cell.content.width(); + if width > *acc { + *acc = width; + } + } + + acc + }); + let len = max_lens.iter().sum::() + n + 1; // +1: reserve some space for scrollbar + let width = len.min(viewport.0 as usize); + + self.widths = max_lens + .into_iter() + .map(|len| Constraint::Length(len as u16)) + .collect(); + + let height = self.matches.len().min(10).min(viewport.1 as usize); + + self.size = (width as u16, height as u16); + + // adjust scroll offsets if size changed + self.adjust_scroll(); + self.recalculate = false; + } + fn adjust_scroll(&mut self) { let win_height = self.size.1 as usize; if let Some(cursor) = self.cursor { @@ -221,43 +261,13 @@ impl Component for Menu { } fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { - let n = self - .options - .first() - .map(|option| option.row().cells.len()) - .unwrap_or_default(); - let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| { - let row = option.row(); - // maintain max for each column - for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { - let width = cell.content.width(); - if width > *acc { - *acc = width; - } - } - - acc - }); - let len = max_lens.iter().sum::() + n + 1; // +1: reserve some space for scrollbar - let width = len.min(viewport.0 as usize); - - self.widths = max_lens - .into_iter() - .map(|len| Constraint::Length(len as u16)) - .collect(); - - let height = self.options.len().min(10).min(viewport.1 as usize); - - self.size = (width as u16, height as u16); - - // adjust scroll offsets if size changed - self.adjust_scroll(); + if viewport != self.viewport || self.recalculate { + self.recalculate_size(viewport); + } Some(self.size) } - // TODO: required size should re-trigger when we filter items so we can draw a smaller menu - fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { let theme = &cx.editor.theme; let style = theme diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 846cefb8..1bab1eae 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -16,8 +16,6 @@ pub struct Popup { } impl Popup { - // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different - // rendering) pub fn new(contents: T) -> Self { Self { contents, @@ -38,20 +36,26 @@ impl Popup { let (width, height) = self.size; + // if there's a orientation preference, use that + // if we're on the top part of the screen, do below + // if we're on the bottom part, do above + // -- make sure frame doesn't stick out of bounds let mut rel_x = position.col as u16; - let rel_y = position.row as u16; + let mut rel_y = position.row as u16; if viewport.width <= rel_x + width { rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width)); - }; + } // TODO: be able to specify orientation preference. We want above for most popups, below // for menus/autocomplete. - if height <= rel_y { - (rel_x, rel_y.saturating_sub(height)) // position above point + if viewport.height > rel_y + height { + rel_y += 1 // position below point } else { - (rel_x, rel_y + 1) // position below point + rel_y = rel_y.saturating_sub(height) // position above point } + + (rel_x, rel_y) } pub fn get_size(&self) -> (u16, u16) { @@ -133,6 +137,9 @@ impl Component for Popup { } fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { + // trigger required_size so we recalculate if the child changed + self.required_size((viewport.width, viewport.height)); + cx.scroll = Some(self.scroll); let (rel_x, rel_y) = self.get_rel_position(viewport, cx); diff --git a/helix-term/src/ui/text.rs b/helix-term/src/ui/text.rs index 65a75a4a..4641fae1 100644 --- a/helix-term/src/ui/text.rs +++ b/helix-term/src/ui/text.rs @@ -5,11 +5,17 @@ use helix_view::graphics::Rect; pub struct Text { contents: String, + size: (u16, u16), + viewport: (u16, u16), } impl Text { pub fn new(contents: String) -> Self { - Self { contents } + Self { + contents, + size: (0, 0), + viewport: (0, 0), + } } } impl Component for Text { @@ -24,9 +30,13 @@ impl Component for Text { } fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { - let contents = tui::text::Text::from(self.contents.clone()); - let width = std::cmp::min(contents.width() as u16, viewport.0); - let height = std::cmp::min(contents.height() as u16, viewport.1); - Some((width, height)) + if viewport != self.viewport { + let contents = tui::text::Text::from(self.contents.clone()); + let width = std::cmp::min(contents.width() as u16, viewport.0); + let height = std::cmp::min(contents.height() as u16, viewport.1); + self.size = (width, height); + self.viewport = viewport; + } + Some(self.size) } }