Unnamed repository; edit this file 'description' to name the repository.
Split out render & cursor methods into a tui renderer
Blaž Hrastnik 2022-05-01
parent 1aa2b02 · commit eadb2ea
-rw-r--r--helix-view/src/compositor.rs24
-rw-r--r--helix-view/src/info.rs7
-rw-r--r--helix-view/src/ui/completion.rs9
-rw-r--r--helix-view/src/ui/editor.rs37
-rw-r--r--helix-view/src/ui/markdown.rs15
-rw-r--r--helix-view/src/ui/menu.rs14
-rw-r--r--helix-view/src/ui/overlay.rs8
-rw-r--r--helix-view/src/ui/picker.rs27
-rw-r--r--helix-view/src/ui/popup.rs15
-rw-r--r--helix-view/src/ui/prompt.rs40
-rw-r--r--helix-view/src/ui/text.rs7
11 files changed, 122 insertions, 81 deletions
diff --git a/helix-view/src/compositor.rs b/helix-view/src/compositor.rs
index e66a8d89..55a15844 100644
--- a/helix-view/src/compositor.rs
+++ b/helix-view/src/compositor.rs
@@ -23,7 +23,7 @@ pub struct Context<'a> {
}
#[cfg(feature = "term")]
-mod term {
+pub mod term {
use super::*;
pub use tui::buffer::Buffer as Surface;
@@ -32,12 +32,24 @@ mod term {
pub surface: &'a mut Surface,
pub scroll: Option<usize>,
}
+
+ pub trait Render {
+ /// Render the component onto the provided surface.
+ fn render(&mut self, area: Rect, ctx: &mut RenderContext);
+
+ // TODO: make required_size be layout() instead and part of this trait?
+
+ /// Get cursor position and cursor kind.
+ fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option<Position>, CursorKind) {
+ (None, CursorKind::Hidden)
+ }
+ }
}
#[cfg(feature = "term")]
pub use term::*;
-pub trait Component: Any + AnyComponent {
+pub trait Component: Any + AnyComponent + Render {
/// Process input events, return true if handled.
fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
EventResult::Ignored(None)
@@ -48,14 +60,6 @@ pub trait Component: Any + AnyComponent {
true
}
- /// Render the component onto the provided surface.
- fn render(&mut self, area: Rect, ctx: &mut RenderContext);
-
- /// Get cursor position and cursor kind.
- fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option<Position>, CursorKind) {
- (None, CursorKind::Hidden)
- }
-
/// May be used by the parent component to compute the child area.
/// viewport is the maximum allowed area, and the child should stay within those bounds.
///
diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs
index 545011f9..9cc5c428 100644
--- a/helix-view/src/info.rs
+++ b/helix-view/src/info.rs
@@ -77,12 +77,13 @@ impl Info {
// term
use crate::{
- compositor::{Component, RenderContext},
+ compositor::{self, Component, RenderContext},
graphics::{Margin, Rect},
};
use tui::widgets::{Block, Borders, Paragraph, Widget};
-impl Component for Info {
+#[cfg(feature = "term")]
+impl compositor::term::Render for Info {
fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) {
let text_style = cx.editor.theme.get("ui.text.info");
let popup_style = cx.editor.theme.get("ui.popup.info");
@@ -117,3 +118,5 @@ impl Component for Info {
.render(inner, cx.surface);
}
}
+
+impl Component for Info {}
diff --git a/helix-view/src/ui/completion.rs b/helix-view/src/ui/completion.rs
index 64ef686e..c6ffe462 100644
--- a/helix-view/src/ui/completion.rs
+++ b/helix-view/src/ui/completion.rs
@@ -1,14 +1,14 @@
-use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
+use crate::compositor::{self, Component, Context, Event, EventResult};
use crate::editor::CompleteAction;
use std::borrow::Cow;
-use helix_core::{Change, Transaction};
use crate::{
graphics::Rect,
input::{KeyCode, KeyEvent},
Document, Editor,
};
+use helix_core::{Change, Transaction};
use crate::commands;
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
@@ -302,8 +302,11 @@ impl Component for Completion {
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
self.popup.required_size(viewport)
}
+}
- fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+#[cfg(feature = "term")]
+impl compositor::term::Render for Completion {
+ fn render(&mut self, area: Rect, cx: &mut compositor::term::RenderContext<'_>) {
self.popup.render(area, cx);
// if we have a selection, render a markdown popup on top/below with info
diff --git a/helix-view/src/ui/editor.rs b/helix-view/src/ui/editor.rs
index 8d479337..7caf56ed 100644
--- a/helix-view/src/ui/editor.rs
+++ b/helix-view/src/ui/editor.rs
@@ -1,11 +1,19 @@
use crate::{
- commands, key,
+ commands, compositor, key,
keymap::{KeymapResult, Keymaps},
ui::{Completion, ProgressSpinners},
};
-use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
+use crate::compositor::{Component, Context, Event, EventResult};
+use crate::{
+ document::{Mode, SCRATCH_BUFFER_NAME},
+ editor::{CompleteAction, CursorShapeConfig},
+ graphics::{CursorKind, Modifier, Rect, Style},
+ input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
+ keyboard::{KeyCode, KeyModifiers},
+ Document, Editor, Theme, View,
+};
use helix_core::{
coords_at_pos, encoding,
graphemes::{
@@ -17,18 +25,8 @@ use helix_core::{
unicode::width::UnicodeWidthStr,
LineEnding, Position, Range, Selection, Transaction,
};
-use crate::{
- document::{Mode, SCRATCH_BUFFER_NAME},
- editor::{CompleteAction, CursorShapeConfig},
- graphics::{CursorKind, Modifier, Rect, Style},
- input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
- keyboard::{KeyCode, KeyModifiers},
- Document, Editor, Theme, View,
-};
use std::borrow::Cow;
-use tui::buffer::Buffer as Surface;
-
pub struct EditorView {
pub keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
@@ -64,7 +62,13 @@ impl EditorView {
pub fn spinners_mut(&mut self) -> &mut ProgressSpinners {
&mut self.spinners
}
+}
+
+#[cfg(feature = "term")]
+use tui::buffer::Buffer as Surface;
+#[cfg(feature = "term")]
+impl EditorView {
pub fn render_view(
&self,
editor: &Editor,
@@ -777,7 +781,9 @@ impl EditorView {
true,
);
}
+}
+impl EditorView {
/// Handle events by looking them up in `self.keymaps`. Returns None
/// if event was handled (a command was executed or a subkeymap was
/// activated). Only KeymapResult::{NotFound, Cancelled} is returned
@@ -948,9 +954,7 @@ impl EditorView {
EventResult::Consumed(None)
}
-}
-impl EditorView {
fn handle_mouse_event(
&mut self,
event: MouseEvent,
@@ -1288,8 +1292,11 @@ impl Component for EditorView {
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
}
}
+}
- fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+#[cfg(feature = "term")]
+impl compositor::term::Render for EditorView {
+ fn render(&mut self, area: Rect, cx: &mut compositor::term::RenderContext<'_>) {
// clear with background color
cx.surface
.set_style(area, cx.editor.theme.get("ui.background"));
diff --git a/helix-view/src/ui/markdown.rs b/helix-view/src/ui/markdown.rs
index def84477..eb5e0a43 100644
--- a/helix-view/src/ui/markdown.rs
+++ b/helix-view/src/ui/markdown.rs
@@ -1,18 +1,18 @@
-use crate::compositor::{Component, RenderContext};
+use crate::compositor::{self, Component, RenderContext};
use tui::text::{Span, Spans, Text};
use std::sync::Arc;
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
-use helix_core::{
- syntax::{self, HighlightEvent, Syntax},
- Rope,
-};
use crate::{
graphics::{Margin, Rect, Style},
Theme,
};
+use helix_core::{
+ syntax::{self, HighlightEvent, Syntax},
+ Rope,
+};
fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
let spans: Vec<_> = text
@@ -255,7 +255,8 @@ impl Markdown {
}
}
-impl Component for Markdown {
+#[cfg(feature = "term")]
+impl compositor::term::Render for Markdown {
fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
use tui::widgets::{Paragraph, Widget, Wrap};
@@ -271,7 +272,9 @@ impl Component for Markdown {
};
par.render(area.inner(&margin), cx.surface);
}
+}
+impl Component for Markdown {
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
let padding = 2;
if padding >= viewport.1 || padding >= viewport.0 {
diff --git a/helix-view/src/ui/menu.rs b/helix-view/src/ui/menu.rs
index de370e0d..d85eb100 100644
--- a/helix-view/src/ui/menu.rs
+++ b/helix-view/src/ui/menu.rs
@@ -1,16 +1,15 @@
-use crate::{ctrl, key, shift};
use crate::compositor::{
- Callback, Component, Compositor, Context, Event, EventResult, RenderContext,
+ self, Callback, Component, Compositor, Context, Event, EventResult, RenderContext,
};
-use tui::widgets::Table;
-
-pub use tui::widgets::{Cell, Row};
+use crate::{ctrl, key, shift};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
use crate::{graphics::Rect, Editor};
+
use tui::layout::Constraint;
+pub use tui::widgets::{Cell, Row};
pub trait Item {
fn label(&self) -> &str;
@@ -263,8 +262,13 @@ impl<T: Item + 'static> Component for Menu<T> {
Some(self.size)
}
+}
+#[cfg(feature = "term")]
+impl<T: Item + 'static> compositor::term::Render for Menu<T> {
fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+ use tui::widgets::Table;
+
let theme = &cx.editor.theme;
let style = theme
.try_get("ui.menu")
diff --git a/helix-view/src/ui/overlay.rs b/helix-view/src/ui/overlay.rs
index c7e153d1..f44374eb 100644
--- a/helix-view/src/ui/overlay.rs
+++ b/helix-view/src/ui/overlay.rs
@@ -1,8 +1,9 @@
-use helix_core::Position;
use crate::{
+ compositor,
graphics::{CursorKind, Rect},
Editor,
};
+use helix_core::Position;
use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
@@ -41,12 +42,15 @@ fn clip_rect_relative(rect: Rect, percent_horizontal: u8, percent_vertical: u8)
}
}
-impl<T: Component + 'static> Component for Overlay<T> {
+#[cfg(feature = "term")]
+impl<T: Component + 'static> compositor::term::Render for Overlay<T> {
fn render(&mut self, area: Rect, ctx: &mut RenderContext<'_>) {
let dimensions = (self.calc_child_size)(area);
self.content.render(dimensions, ctx)
}
+}
+impl<T: Component + 'static> Component for Overlay<T> {
fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
let area = Rect {
x: 0,
diff --git a/helix-view/src/ui/picker.rs b/helix-view/src/ui/picker.rs
index 2fbacab3..91022e44 100644
--- a/helix-view/src/ui/picker.rs
+++ b/helix-view/src/ui/picker.rs
@@ -1,13 +1,11 @@
+use crate::compositor::{self, Component, Compositor, Context, Event, EventResult, RenderContext};
use crate::{
ctrl, key, shift,
ui::{self, EditorView},
};
-use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext};
-use tui::widgets::{Block, BorderType, Borders};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
-use tui::widgets::Widget;
use std::time::Instant;
use std::{
@@ -19,12 +17,12 @@ use std::{
};
use crate::ui::{Prompt, PromptEvent};
-use helix_core::{movement::Direction, Position};
use crate::{
editor::Action,
graphics::{Color, CursorKind, Margin, Modifier, Rect, Style},
Document, Editor,
};
+use helix_core::{movement::Direction, Position};
pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
/// Biggest file size to preview in bytes
@@ -159,8 +157,11 @@ impl<T> FilePicker<T> {
}
}
-impl<T: 'static> Component for FilePicker<T> {
+#[cfg(feature = "term")]
+impl<T: 'static> compositor::term::Render for FilePicker<T> {
fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+ use tui::widgets::Widget;
+ use tui::widgets::{Block, Borders};
// +---------+ +---------+
// |prompt | |preview |
// +---------+ | |
@@ -260,15 +261,17 @@ impl<T: 'static> Component for FilePicker<T> {
}
}
+ fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
+ self.picker.cursor(area, ctx)
+ }
+}
+
+impl<T: 'static> Component for FilePicker<T> {
fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
// TODO: keybinds for scrolling preview
self.picker.handle_event(event, ctx)
}
- fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
- self.picker.cursor(area, ctx)
- }
-
fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
let picker_width = if width > MIN_AREA_WIDTH_FOR_PREVIEW {
width / 2
@@ -548,8 +551,13 @@ impl<T: 'static> Component for Picker<T> {
EventResult::Consumed(None)
}
+}
+#[cfg(feature = "term")]
+impl<T: 'static> compositor::term::Render for Picker<T> {
fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+ use tui::widgets::Widget;
+ use tui::widgets::{Block, BorderType, Borders};
let text_style = cx.editor.theme.get("ui.text");
let selected = cx.editor.theme.get("ui.text.focus");
let highlighted = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);
@@ -639,6 +647,7 @@ impl<T: 'static> Component for Picker<T> {
}
fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
+ use tui::widgets::{Block, Borders};
let block = Block::default().borders(Borders::ALL);
// calculate the inner area inside the box
let inner = block.inner(area);
diff --git a/helix-view/src/ui/popup.rs b/helix-view/src/ui/popup.rs
index c8de7e74..eb6823a7 100644
--- a/helix-view/src/ui/popup.rs
+++ b/helix-view/src/ui/popup.rs
@@ -1,11 +1,11 @@
+use crate::compositor::{self, Callback, Component, Context, Event, EventResult, RenderContext};
use crate::{ctrl, key};
-use crate::compositor::{Callback, Component, Context, Event, EventResult, RenderContext};
-use helix_core::Position;
use crate::{
graphics::{Margin, Rect},
Editor,
};
+use helix_core::Position;
// TODO: share logic with Menu, it's essentially Popup(render_fn), but render fn needs to return
// a width/height hint. maybe Popup(Box<Component>)
@@ -175,6 +175,13 @@ impl<T: Component> Component for Popup<T> {
Some(self.size)
}
+ fn id(&self) -> Option<&'static str> {
+ Some(self.id)
+ }
+}
+
+#[cfg(feature = "term")]
+impl<T: Component> compositor::term::Render for Popup<T> {
fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) {
// trigger required_size so we recalculate if the child changed
self.required_size((viewport.width, viewport.height));
@@ -193,8 +200,4 @@ impl<T: Component> Component for Popup<T> {
let inner = area.inner(&self.margin);
self.contents.render(inner, cx);
}
-
- fn id(&self) -> Option<&'static str> {
- Some(self.id)
- }
}
diff --git a/helix-view/src/ui/prompt.rs b/helix-view/src/ui/prompt.rs
index 183d5de4..2d73fb1e 100644
--- a/helix-view/src/ui/prompt.rs
+++ b/helix-view/src/ui/prompt.rs
@@ -1,9 +1,8 @@
-use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext};
+use crate::compositor::{self, Component, Compositor, Context, Event, EventResult, RenderContext};
use crate::input::KeyEvent;
use crate::keyboard::KeyCode;
use crate::{alt, ctrl, key, shift, ui};
use std::{borrow::Cow, ops::RangeFrom};
-use tui::widgets::{Block, Borders, Widget};
use crate::{
graphics::{CursorKind, Margin, Rect},
@@ -324,8 +323,11 @@ impl Prompt {
const BASE_WIDTH: u16 = 30;
-impl Prompt {
- pub fn render_prompt(&self, area: Rect, cx: &mut RenderContext<'_>) {
+#[cfg(feature = "term")]
+impl compositor::term::Render for Prompt {
+ fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
+ use tui::widgets::{Block, Borders, Widget};
+
let theme = &cx.editor.theme;
let prompt_color = theme.get("ui.text");
let completion_color = theme.get("ui.statusline");
@@ -438,6 +440,19 @@ impl Prompt {
prompt_color,
);
}
+
+ fn cursor(&self, area: Rect, _editor: &Editor) -> (Option<Position>, CursorKind) {
+ let line = area.height as usize - 1;
+ (
+ Some(Position::new(
+ area.y as usize + line,
+ area.x as usize
+ + self.prompt.len()
+ + UnicodeWidthStr::width(&self.line[..self.cursor]),
+ )),
+ CursorKind::Block,
+ )
+ }
}
impl Component for Prompt {
@@ -545,21 +560,4 @@ impl Component for Prompt {
EventResult::Consumed(None)
}
-
- fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
- self.render_prompt(area, cx)
- }
-
- fn cursor(&self, area: Rect, _editor: &Editor) -> (Option<Position>, CursorKind) {
- let line = area.height as usize - 1;
- (
- Some(Position::new(
- area.y as usize + line,
- area.x as usize
- + self.prompt.len()
- + UnicodeWidthStr::width(&self.line[..self.cursor]),
- )),
- CursorKind::Block,
- )
- }
}
diff --git a/helix-view/src/ui/text.rs b/helix-view/src/ui/text.rs
index 224aa230..52082080 100644
--- a/helix-view/src/ui/text.rs
+++ b/helix-view/src/ui/text.rs
@@ -1,4 +1,4 @@
-use crate::compositor::{Component, RenderContext};
+use crate::compositor::{self, Component, RenderContext};
use crate::graphics::Rect;
pub struct Text {
@@ -27,7 +27,8 @@ impl From<tui::text::Text<'static>> for Text {
}
}
-impl Component for Text {
+#[cfg(feature = "term")]
+impl compositor::term::Render for Text {
fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) {
use tui::widgets::{Paragraph, Widget, Wrap};
@@ -36,7 +37,9 @@ impl Component for Text {
par.render(area, cx.surface);
}
+}
+impl Component for Text {
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
if viewport != self.viewport {
let width = std::cmp::min(self.contents.width() as u16, viewport.0);