aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/ui/menu.rs76
-rw-r--r--helix-term/src/ui/popup.rs21
-rw-r--r--helix-term/src/ui/text.rs20
3 files changed, 72 insertions, 45 deletions
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<T: Item> {
scroll: usize,
size: (u16, u16),
+ viewport: (u16, u16),
+ recalculate: bool,
}
impl<T: Item> Menu<T> {
@@ -51,6 +53,8 @@ impl<T: Item> Menu<T> {
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<T: Item> Menu<T> {
// reset cursor position
self.cursor = None;
self.scroll = 0;
+ self.recalculate = true;
}
pub fn move_up(&mut self) {
@@ -99,6 +104,41 @@ impl<T: Item> Menu<T> {
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::<usize>() + 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<T: Item + 'static> Component for Menu<T> {
}
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::<usize>() + 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<T: Component> {
}
impl<T: Component> Popup<T> {
- // 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<T: Component> Popup<T> {
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<T: Component> Component for Popup<T> {
}
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)
}
}