ui: Share popup code with menu.

Menu is now just wrapped in a popup.
pull/8/head
Blaž Hrastnik 4 years ago
parent 11c4e0b053
commit 0b85c16be9

@ -1117,16 +1117,8 @@ pub fn completion(cx: &mut Context) {
}, },
); );
cx.callback = Some(Box::new( let popup = Popup::new(Box::new(menu));
move |compositor: &mut Compositor, editor: &mut Editor| { cx.push_layer(Box::new(popup));
if let Some(mut pos) = editor.cursor_position() {
pos.row += 1; // shift down by one row
menu.set_position(pos);
};
compositor.push(Box::new(menu));
},
));
// TODO!: when iterating over items, show the docs in popup // TODO!: when iterating over items, show the docs in popup
@ -1171,22 +1163,9 @@ pub fn hover(cx: &mut Context) {
// skip if contents empty // skip if contents empty
// Popup: box frame + Box<Component> for internal content. let contents = ui::Text::new(contents);
// it will use the contents.size_hint/required size to figure out sizing & positioning let mut popup = Popup::new(Box::new(contents));
// can also use render_buffer to render the content. cx.push_layer(Box::new(popup));
// render_buffer(highlights/scopes, text, surface, theme)
//
let mut popup = Popup::new(contents);
cx.callback = Some(Box::new(
move |compositor: &mut Compositor, editor: &mut Editor| {
if let Some(mut pos) = editor.cursor_position() {
popup.set_position(pos);
};
compositor.push(Box::new(popup));
},
));
} }
} }

@ -44,7 +44,9 @@ pub struct Context<'a> {
pub trait Component { pub trait Component {
/// Process input events, return true if handled. /// Process input events, return true if handled.
fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult; fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
EventResult::Ignored
}
// , args: () // , args: ()
/// Should redraw? Useful for saving redraw cycles if we know component didn't change. /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
@ -57,6 +59,10 @@ pub trait Component {
fn cursor_position(&self, area: Rect, ctx: &Editor) -> Option<Position> { fn cursor_position(&self, area: Rect, ctx: &Editor) -> Option<Position> {
None None
} }
fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
None
}
} }
// For v1: // For v1:

@ -20,8 +20,6 @@ pub struct Menu<T> {
cursor: usize, cursor: usize,
position: Position,
format_fn: Box<dyn Fn(&T) -> Cow<str>>, format_fn: Box<dyn Fn(&T) -> Cow<str>>,
callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>, callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
} }
@ -37,16 +35,11 @@ impl<T> Menu<T> {
Self { Self {
options, options,
cursor: 0, cursor: 0,
position: Position::default(),
format_fn: Box::new(format_fn), format_fn: Box::new(format_fn),
callback_fn: Box::new(callback_fn), callback_fn: Box::new(callback_fn),
} }
} }
pub fn set_position(&mut self, pos: Position) {
self.position = pos;
}
pub fn move_up(&mut self) { pub fn move_up(&mut self) {
self.cursor = self.cursor.saturating_sub(1); self.cursor = self.cursor.saturating_sub(1);
} }
@ -151,31 +144,18 @@ impl<T> Component for Menu<T> {
// EventResult::Consumed(None) // EventResult::Consumed(None)
EventResult::Ignored EventResult::Ignored
} }
fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
// render a box at x, y. Width equal to max width of item.
// initially limit to n items, add support for scrolling
//
const MAX: usize = 5;
let rows = std::cmp::min(self.options.len(), MAX) as u16;
let area = Rect::new(self.position.col as u16, self.position.row as u16, 30, rows);
// clear area fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
let background = cx.editor.theme.get("ui.popup"); const MAX: usize = 5;
for y in area.top()..area.bottom() { let height = std::cmp::min(self.options.len(), MAX);
for x in area.left()..area.right() { Some((30, height))
let cell = surface.get_mut(x, y);
cell.reset();
// cell.symbol.clear();
cell.set_style(background);
}
} }
// -- Render the contents: fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
let selected = Style::default().fg(Color::Rgb(255, 255, 255)); let selected = Style::default().fg(Color::Rgb(255, 255, 255));
for (i, option) in self.options.iter().take(rows as usize).enumerate() { for (i, option) in self.options.iter().take(area.height as usize).enumerate() {
// TODO: set bg for the whole row if selected // TODO: set bg for the whole row if selected
surface.set_stringn( surface.set_stringn(
area.x, area.x,

@ -3,12 +3,14 @@ mod menu;
mod picker; mod picker;
mod popup; mod popup;
mod prompt; mod prompt;
mod text;
pub use editor::EditorView; pub use editor::EditorView;
pub use menu::Menu; pub use menu::Menu;
pub use picker::Picker; pub use picker::Picker;
pub use popup::Popup; pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent}; pub use prompt::{Prompt, PromptEvent};
pub use text::Text;
pub use tui::layout::Rect; pub use tui::layout::Rect;
pub use tui::style::{Color, Modifier, Style}; pub use tui::style::{Color, Modifier, Style};

@ -16,28 +16,28 @@ use helix_view::Editor;
// a width/height hint. maybe Popup(Box<Component>) // a width/height hint. maybe Popup(Box<Component>)
pub struct Popup { pub struct Popup {
contents: String, contents: Box<dyn Component>,
position: Position, position: Option<Position>,
} }
impl Popup { impl Popup {
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
// rendering) // rendering)
pub fn new(contents: String) -> Self { pub fn new(contents: Box<dyn Component>) -> Self {
Self { Self {
contents, contents,
position: Position::default(), position: None,
} }
} }
pub fn set_position(&mut self, pos: Position) { pub fn set_position(&mut self, pos: Option<Position>) {
self.position = pos; self.position = pos;
} }
} }
impl Component for Popup { impl Component for Popup {
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
let event = match event { let key = match event {
Event::Key(event) => event, Event::Key(event) => event,
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
}; };
@ -49,7 +49,7 @@ impl Component for Popup {
}, },
))); )));
match event { match key {
// esc or ctrl-c aborts the completion and closes the menu // esc or ctrl-c aborts the completion and closes the menu
KeyEvent { KeyEvent {
code: KeyCode::Esc, .. code: KeyCode::Esc, ..
@ -60,29 +60,37 @@ impl Component for Popup {
} => { } => {
return close_fn; return close_fn;
} }
_ => (), _ => self.contents.handle_event(event, cx),
} }
// for some events, we want to process them but send ignore, specifically all input except // for some events, we want to process them but send ignore, specifically all input except
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll. // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
// EventResult::Consumed(None)
EventResult::Consumed(None)
} }
fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
use tui::text::Text; use tui::text::Text;
use tui::widgets::{Paragraph, Widget, Wrap}; use tui::widgets::{Paragraph, Widget, Wrap};
let contents = Text::from(self.contents.clone()); let position = self
.position
.or_else(|| cx.editor.cursor_position())
.unwrap_or_default();
let width = contents.width().min(150) as u16; let (width, height) = self
let height = contents.height().min(13) as u16; .contents
.size_hint(viewport)
.expect("Component needs size_hint implemented in order to be embedded in a popup");
let width = width.min(150) as u16;
let height = height.min(13) as u16;
// -- make sure frame doesn't stick out of bounds // -- make sure frame doesn't stick out of bounds
let mut rel_x = self.position.col as u16; let mut rel_x = position.col as u16;
let mut rel_y = self.position.row as u16; let mut rel_y = position.row as u16;
if viewport.width <= rel_x + width { if viewport.width <= rel_x + width {
rel_x -= ((rel_x + width) - viewport.width) rel_x -= ((rel_x + width) - viewport.width)
}; };
// TODO: be able to specify orientation preference. We want above for most popups, below
// for menus/autocomplete.
if height <= rel_y { if height <= rel_y {
rel_y -= height // position above point rel_y -= height // position above point
} else { } else {
@ -104,13 +112,6 @@ impl Component for Popup {
} }
} }
// -- Render the contents: self.contents.render(area, surface, cx);
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
let par = Paragraph::new(contents).wrap(Wrap { trim: false });
// .scroll(x, y) offsets
par.render(area, surface);
} }
} }

@ -0,0 +1,41 @@
use crate::compositor::{Component, Compositor, Context, EventResult};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use tui::buffer::Buffer as Surface;
use tui::{
layout::Rect,
style::{Color, Style},
widgets::{Block, Borders},
};
use std::borrow::Cow;
use helix_core::Position;
use helix_view::Editor;
pub struct Text {
contents: String,
}
impl Text {
pub fn new(contents: String) -> Self {
Self { contents }
}
}
impl Component for Text {
fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
use tui::widgets::{Paragraph, Widget, Wrap};
let contents = tui::text::Text::from(self.contents.clone());
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
let par = Paragraph::new(contents).wrap(Wrap { trim: false });
// .scroll(x, y) offsets
par.render(area, surface);
}
fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
let contents = tui::text::Text::from(self.contents.clone());
Some((contents.width(), contents.height()))
}
}
Loading…
Cancel
Save