ui: Trigger recalculate_size per popup render so contents can readjust
This commit is contained in:
parent
f2c73d1567
commit
32977ed341
3 changed files with 72 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue