on master help sign in
ui: Share popup code with menu.

Menu is now just wrapped in a popup.

commit0b85c16be91d9326876b2e4a1ae6bdc1381d700d authorBlaž Hrastnik <blaz@mxxn.io> dateTue 2 Mar 2021, 18:24 (6 years ago) parent11c4e0b
6 files changed+86−77
helix-term/src/commands.rs+5−26 helix-term/src/compositor.rs+7−1 helix-term/src/ui/menu.rs+7−27 helix-term/src/ui/mod.rs+2 helix-term/src/ui/popup.rs+24−23 helix-term/src/ui/text.rs+41
helix-term/src/commands.rs+5−26
@@ -1117,16 +1117,8 @@ pub fn completion(cx: &mut Context) {
11171117 },
11181118 );
11191119
1120− cx.callback = Some(Box::new(
1121− move |compositor: &mut Compositor, editor: &mut Editor| {
1122− if let Some(mut pos) = editor.cursor_position() {
1123− pos.row += 1; // shift down by one row
1124− menu.set_position(pos);
1125− };
1126−
1127− compositor.push(Box::new(menu));
1128− },
1129− ));
1120+ let popup = Popup::new(Box::new(menu));
1121+ cx.push_layer(Box::new(popup));
11301122
11311123 // TODO!: when iterating over items, show the docs in popup
11321124
@@ -1171,22 +1163,9 @@ pub fn hover(cx: &mut Context) {
11711163
11721164 // skip if contents empty
11731165
1174− // Popup: box frame + Box<Component> for internal content.
1175− // it will use the contents.size_hint/required size to figure out sizing & positioning
1176− // can also use render_buffer to render the content.
1177− // render_buffer(highlights/scopes, text, surface, theme)
1178− //
1179− let mut popup = Popup::new(contents);
1180−
1181− cx.callback = Some(Box::new(
1182− move |compositor: &mut Compositor, editor: &mut Editor| {
1183− if let Some(mut pos) = editor.cursor_position() {
1184− popup.set_position(pos);
1185− };
1186−
1187− compositor.push(Box::new(popup));
1188− },
1189− ));
1166+ let contents = ui::Text::new(contents);
1167+ let mut popup = Popup::new(Box::new(contents));
1168+ cx.push_layer(Box::new(popup));
11901169 }
11911170}
11921171
helix-term/src/compositor.rs+7−1
@@ -44,7 +44,9 @@ pub struct Context<'a> {
4444
4545pub trait Component {
4646 /// Process input events, return true if handled.
47− fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult;
47+ fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
48+ EventResult::Ignored
49+ }
4850 // , args: ()
4951
5052 /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
@@ -57,6 +59,10 @@ pub trait Component {
5759 fn cursor_position(&self, area: Rect, ctx: &Editor) -> Option<Position> {
5860 None
5961 }
62+
63+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
64+ None
65+ }
6066}
6167
6268// For v1:
helix-term/src/ui/menu.rs+7−27
@@ -20,8 +20,6 @@ pub struct Menu<T> {
2020
2121 cursor: usize,
2222
23− position: Position,
24−
2523 format_fn: Box<dyn Fn(&T) -> Cow<str>>,
2624 callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
2725}
@@ -37,16 +35,11 @@ impl<T> Menu<T> {
3735 Self {
3836 options,
3937 cursor: 0,
40− position: Position::default(),
4138 format_fn: Box::new(format_fn),
4239 callback_fn: Box::new(callback_fn),
4340 }
4441 }
4542
46− pub fn set_position(&mut self, pos: Position) {
47− self.position = pos;
48− }
49−
5043 pub fn move_up(&mut self) {
5144 self.cursor = self.cursor.saturating_sub(1);
5245 }
@@ -151,31 +144,18 @@ impl<T> Component for Menu<T> {
151144 // EventResult::Consumed(None)
152145 EventResult::Ignored
153146 }
154− fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
155− // render a box at x, y. Width equal to max width of item.
156− // initially limit to n items, add support for scrolling
157− //
158− const MAX: usize = 5;
159− let rows = std::cmp::min(self.options.len(), MAX) as u16;
160− let area = Rect::new(self.position.col as u16, self.position.row as u16, 30, rows);
161−
162− // clear area
163− let background = cx.editor.theme.get("ui.popup");
164− for y in area.top()..area.bottom() {
165− for x in area.left()..area.right() {
166− let cell = surface.get_mut(x, y);
167− cell.reset();
168− // cell.symbol.clear();
169− cell.set_style(background);
170− }
171− }
172147
173− // -- Render the contents:
148+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
149+ const MAX: usize = 5;
150+ let height = std::cmp::min(self.options.len(), MAX);
151+ Some((30, height))
152+ }
174153
154+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
175155 let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
176156 let selected = Style::default().fg(Color::Rgb(255, 255, 255));
177157
178− for (i, option) in self.options.iter().take(rows as usize).enumerate() {
158+ for (i, option) in self.options.iter().take(area.height as usize).enumerate() {
179159 // TODO: set bg for the whole row if selected
180160 surface.set_stringn(
181161 area.x,
helix-term/src/ui/mod.rs+2
@@ -3,12 +3,14 @@ mod menu;
33mod picker;
44mod popup;
55mod prompt;
6+mod text;
67
78pub use editor::EditorView;
89pub use menu::Menu;
910pub use picker::Picker;
1011pub use popup::Popup;
1112pub use prompt::{Prompt, PromptEvent};
13+pub use text::Text;
1214
1315pub use tui::layout::Rect;
1416pub use tui::style::{Color, Modifier, Style};
helix-term/src/ui/popup.rs+24−23
@@ -16,28 +16,28 @@ use helix_view::Editor;
1616// a width/height hint. maybe Popup(Box<Component>)
1717
1818pub struct Popup {
19− contents: String,
20− position: Position,
19+ contents: Box<dyn Component>,
20+ position: Option<Position>,
2121}
2222
2323impl Popup {
2424 // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
2525 // rendering)
26− pub fn new(contents: String) -> Self {
26+ pub fn new(contents: Box<dyn Component>) -> Self {
2727 Self {
2828 contents,
29− position: Position::default(),
29+ position: None,
3030 }
3131 }
3232
33− pub fn set_position(&mut self, pos: Position) {
33+ pub fn set_position(&mut self, pos: Option<Position>) {
3434 self.position = pos;
3535 }
3636}
3737
3838impl Component for Popup {
3939 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
40− let event = match event {
40+ let key = match event {
4141 Event::Key(event) => event,
4242 _ => return EventResult::Ignored,
4343 };
@@ -49,7 +49,7 @@ impl Component for Popup {
4949 },
5050 )));
5151
52− match event {
52+ match key {
5353 // esc or ctrl-c aborts the completion and closes the menu
5454 KeyEvent {
5555 code: KeyCode::Esc, ..
@@ -60,29 +60,37 @@ impl Component for Popup {
6060 } => {
6161 return close_fn;
6262 }
63− _ => (),
63+ _ => self.contents.handle_event(event, cx),
6464 }
6565 // for some events, we want to process them but send ignore, specifically all input except
6666 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
67− // EventResult::Consumed(None)
68− EventResult::Consumed(None)
6967 }
7068 fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
7169 use tui::text::Text;
7270 use tui::widgets::{Paragraph, Widget, Wrap};
7371
74− let contents = Text::from(self.contents.clone());
72+ let position = self
73+ .position
74+ .or_else(|| cx.editor.cursor_position())
75+ .unwrap_or_default();
7576
76− let width = contents.width().min(150) as u16;
77− let height = contents.height().min(13) as u16;
77+ let (width, height) = self
78+ .contents
79+ .size_hint(viewport)
80+ .expect("Component needs size_hint implemented in order to be embedded in a popup");
81+
82+ let width = width.min(150) as u16;
83+ let height = height.min(13) as u16;
7884
7985 // -- make sure frame doesn't stick out of bounds
80− let mut rel_x = self.position.col as u16;
81− let mut rel_y = self.position.row as u16;
86+ let mut rel_x = position.col as u16;
87+ let mut rel_y = position.row as u16;
8288 if viewport.width <= rel_x + width {
8389 rel_x -= ((rel_x + width) - viewport.width)
8490 };
8591
92+ // TODO: be able to specify orientation preference. We want above for most popups, below
93+ // for menus/autocomplete.
8694 if height <= rel_y {
8795 rel_y -= height // position above point
8896 } else {
@@ -104,13 +112,6 @@ impl Component for Popup {
104112 }
105113 }
106114
107− // -- Render the contents:
108−
109− let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
110−
111− let par = Paragraph::new(contents).wrap(Wrap { trim: false });
112− // .scroll(x, y) offsets
113−
114− par.render(area, surface);
115+ self.contents.render(area, surface, cx);
115116 }
116117}
helix-term/src/ui/text.rsnew file+41
@@ -0,0 +1,41 @@
1+use crate::compositor::{Component, Compositor, Context, EventResult};
2+use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
3+use tui::buffer::Buffer as Surface;
4+use tui::{
5+ layout::Rect,
6+ style::{Color, Style},
7+ widgets::{Block, Borders},
8+};
9+
10+use std::borrow::Cow;
11+
12+use helix_core::Position;
13+use helix_view::Editor;
14+
15+pub struct Text {
16+ contents: String,
17+}
18+
19+impl Text {
20+ pub fn new(contents: String) -> Self {
21+ Self { contents }
22+ }
23+}
24+impl Component for Text {
25+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
26+ use tui::widgets::{Paragraph, Widget, Wrap};
27+ let contents = tui::text::Text::from(self.contents.clone());
28+
29+ let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
30+
31+ let par = Paragraph::new(contents).wrap(Wrap { trim: false });
32+ // .scroll(x, y) offsets
33+
34+ par.render(area, surface);
35+ }
36+
37+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
38+ let contents = tui::text::Text::from(self.contents.clone());
39+ Some((contents.width(), contents.height()))
40+ }
41+}