Implement a separate RenderContext

This commit is contained in:
Blaž Hrastnik 2022-03-27 13:46:41 +09:00
parent 7622643117
commit 3c0e11d69e
No known key found for this signature in database
GPG Key ID: 1238B9C4AD889640
14 changed files with 52 additions and 44 deletions

View File

@ -209,7 +209,6 @@ fn render(&mut self) {
let mut cx = crate::compositor::Context { let mut cx = crate::compositor::Context {
editor: &mut self.editor, editor: &mut self.editor,
jobs: &mut self.jobs, jobs: &mut self.jobs,
scroll: None,
}; };
self.compositor.render(&mut cx); self.compositor.render(&mut cx);
@ -358,7 +357,6 @@ pub fn handle_idle_timeout(&mut self) {
let mut cx = crate::compositor::Context { let mut cx = crate::compositor::Context {
editor: &mut self.editor, editor: &mut self.editor,
jobs: &mut self.jobs, jobs: &mut self.jobs,
scroll: None,
}; };
if let EventResult::Consumed(_) = editor_view.handle_idle_timeout(&mut cx) { if let EventResult::Consumed(_) = editor_view.handle_idle_timeout(&mut cx) {
self.render(); self.render();
@ -369,7 +367,6 @@ pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::
let mut cx = crate::compositor::Context { let mut cx = crate::compositor::Context {
editor: &mut self.editor, editor: &mut self.editor,
jobs: &mut self.jobs, jobs: &mut self.jobs,
scroll: None,
}; };
// Handle key events // Handle key events
let should_redraw = match event { let should_redraw = match event {

View File

@ -164,7 +164,6 @@ pub fn execute(&self, cx: &mut Context) {
let mut cx = compositor::Context { let mut cx = compositor::Context {
editor: cx.editor, editor: cx.editor,
jobs: cx.jobs, jobs: cx.jobs,
scroll: None,
}; };
if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) { if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) {
cx.editor.set_error(format!("{}", e)); cx.editor.set_error(format!("{}", e));

View File

@ -21,10 +21,14 @@ pub enum EventResult {
pub struct Context<'a> { pub struct Context<'a> {
pub editor: &'a mut Editor, pub editor: &'a mut Editor,
pub scroll: Option<usize>,
pub jobs: &'a mut Jobs, pub jobs: &'a mut Jobs,
} }
pub struct RenderContext<'a> {
pub editor: &'a Editor,
pub scroll: Option<usize>,
}
pub trait Component: Any + AnyComponent { pub trait Component: Any + AnyComponent {
/// 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 {
@ -38,7 +42,7 @@ fn should_update(&self) -> bool {
} }
/// Render the component onto the provided surface. /// Render the component onto the provided surface.
fn render(&mut self, area: Rect, frame: &mut Surface, ctx: &mut Context); fn render(&mut self, area: Rect, frame: &mut Surface, ctx: &mut RenderContext);
/// Get cursor position and cursor kind. /// Get cursor position and cursor kind.
fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option<Position>, CursorKind) { fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option<Position>, CursorKind) {
@ -169,18 +173,25 @@ pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool {
} }
pub fn render(&mut self, cx: &mut Context) { pub fn render(&mut self, cx: &mut Context) {
self.terminal let area = self
.terminal
.autoresize() .autoresize()
.expect("Unable to determine terminal size"); .expect("Unable to determine terminal size");
// TODO: need to recalculate view tree if necessary // if the terminal size suddenly changed, we need to trigger a resize
cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline
let surface = self.terminal.current_buffer_mut(); let surface = self.terminal.current_buffer_mut();
let area = *surface.area(); // let area = *surface.area();
let mut cx = RenderContext {
editor: cx.editor,
scroll: None,
};
for layer in &mut self.layers { for layer in &mut self.layers {
layer.render(area, surface, cx); layer.render(area, surface, &mut cx);
} }
let (pos, kind) = self.cursor(area, cx.editor); let (pos, kind) = self.cursor(area, cx.editor);

View File

@ -1,4 +1,4 @@
use crate::compositor::{Component, Context, EventResult}; use crate::compositor::{Component, Context, EventResult, RenderContext};
use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::event::{Event, KeyCode, KeyEvent};
use helix_view::editor::CompleteAction; use helix_view::editor::CompleteAction;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
@ -301,7 +301,7 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
self.popup.required_size(viewport) self.popup.required_size(viewport)
} }
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
self.popup.render(area, surface, cx); self.popup.render(area, surface, cx);
// if we have a selection, render a markdown popup on top/below with info // if we have a selection, render a markdown popup on top/below with info
@ -311,7 +311,7 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
// --- // ---
// option.documentation // option.documentation
let (view, doc) = current!(cx.editor); let (view, doc) = current_ref!(cx.editor);
let language = doc let language = doc
.language() .language()
.and_then(|scope| scope.strip_prefix("source.")) .and_then(|scope| scope.strip_prefix("source."))
@ -369,7 +369,7 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
None => return, None => return,
}; };
let (popup_x, popup_y) = self.popup.get_rel_position(area, cx); let (popup_x, popup_y) = self.popup.get_rel_position(area, cx.editor);
let (popup_width, _popup_height) = self.popup.get_size(); let (popup_width, _popup_height) = self.popup.get_size();
let mut width = area let mut width = area
.width .width

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
commands, commands,
compositor::{Component, Context, EventResult}, compositor::{Component, Context, EventResult, RenderContext},
key, key,
keymap::{KeymapResult, Keymaps}, keymap::{KeymapResult, Keymaps},
ui::{Completion, ProgressSpinners}, ui::{Completion, ProgressSpinners},
@ -1201,7 +1201,6 @@ fn handle_event(
let mut cx = Context { let mut cx = Context {
editor: cx.editor, editor: cx.editor,
jobs: cx.jobs, jobs: cx.jobs,
scroll: None,
}; };
let res = completion.handle_event(event, &mut cx); let res = completion.handle_event(event, &mut cx);
@ -1288,12 +1287,10 @@ fn handle_event(
} }
} }
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
// clear with background color // clear with background color
surface.set_style(area, cx.editor.theme.get("ui.background")); surface.set_style(area, cx.editor.theme.get("ui.background"));
let config = cx.editor.config(); let config = cx.editor.config();
// if the terminal size suddenly changed, we need to trigger a resize
cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline
for (view, is_focused) in cx.editor.tree.views() { for (view, is_focused) in cx.editor.tree.views() {
let doc = cx.editor.document(view.doc).unwrap(); let doc = cx.editor.document(view.doc).unwrap();
@ -1301,9 +1298,10 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
} }
if config.auto_info { if config.auto_info {
if let Some(mut info) = cx.editor.autoinfo.take() { // TODO: drop &mut self on render
if let Some(mut info) = cx.editor.autoinfo.clone() {
info.render(area, surface, cx); info.render(area, surface, cx);
cx.editor.autoinfo = Some(info) // cx.editor.autoinfo = Some(info)
} }
} }

View File

@ -1,11 +1,11 @@
use crate::compositor::{Component, Context}; use crate::compositor::{Component, RenderContext};
use helix_view::graphics::{Margin, Rect}; use helix_view::graphics::{Margin, Rect};
use helix_view::info::Info; use helix_view::info::Info;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use tui::widgets::{Block, Borders, Paragraph, Widget}; use tui::widgets::{Block, Borders, Paragraph, Widget};
impl Component for Info { impl Component for Info {
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
let text_style = cx.editor.theme.get("ui.text.info"); let text_style = cx.editor.theme.get("ui.text.info");
let popup_style = cx.editor.theme.get("ui.popup.info"); let popup_style = cx.editor.theme.get("ui.popup.info");

View File

@ -1,4 +1,4 @@
use crate::compositor::{Component, Context}; use crate::compositor::{Component, RenderContext};
use tui::{ use tui::{
buffer::Buffer as Surface, buffer::Buffer as Surface,
text::{Span, Spans, Text}, text::{Span, Spans, Text},
@ -259,7 +259,7 @@ fn parse(&self, theme: Option<&Theme>) -> tui::text::Text<'_> {
} }
impl Component for Markdown { impl Component for Markdown {
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
use tui::widgets::{Paragraph, Widget, Wrap}; use tui::widgets::{Paragraph, Widget, Wrap};
let text = self.parse(Some(&cx.editor.theme)); let text = self.parse(Some(&cx.editor.theme));

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
compositor::{Callback, Component, Compositor, Context, EventResult}, compositor::{Callback, Component, Compositor, Context, EventResult, RenderContext},
ctrl, key, shift, ctrl, key, shift,
}; };
use crossterm::event::Event; use crossterm::event::Event;
@ -265,7 +265,7 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
Some(self.size) Some(self.size)
} }
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
let theme = &cx.editor.theme; let theme = &cx.editor.theme;
let style = theme let style = theme
.try_get("ui.menu") .try_get("ui.menu")

View File

@ -6,7 +6,7 @@
}; };
use tui::buffer::Buffer; use tui::buffer::Buffer;
use crate::compositor::{Component, Context, EventResult}; use crate::compositor::{Component, Context, EventResult, RenderContext};
/// Contains a component placed in the center of the parent component /// Contains a component placed in the center of the parent component
pub struct Overlay<T> { pub struct Overlay<T> {
@ -44,7 +44,7 @@ fn mul_and_cast(size: u16, factor: u8) -> u16 {
} }
impl<T: Component + 'static> Component for Overlay<T> { impl<T: Component + 'static> Component for Overlay<T> {
fn render(&mut self, area: Rect, frame: &mut Buffer, ctx: &mut Context) { fn render(&mut self, area: Rect, frame: &mut Buffer, ctx: &mut RenderContext<'_>) {
let dimensions = (self.calc_child_size)(area); let dimensions = (self.calc_child_size)(area);
self.content.render(dimensions, frame, ctx) self.content.render(dimensions, frame, ctx)
} }

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
compositor::{Component, Compositor, Context, EventResult}, compositor::{Component, Compositor, Context, EventResult, RenderContext},
ctrl, key, shift, ctrl, key, shift,
ui::{self, EditorView}, ui::{self, EditorView},
}; };
@ -164,7 +164,7 @@ fn get_preview<'picker, 'editor>(
} }
impl<T: 'static> Component for FilePicker<T> { impl<T: 'static> Component for FilePicker<T> {
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
// +---------+ +---------+ // +---------+ +---------+
// |prompt | |preview | // |prompt | |preview |
// +---------+ | | // +---------+ | |
@ -552,7 +552,7 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
let text_style = cx.editor.theme.get("ui.text"); let text_style = cx.editor.theme.get("ui.text");
let selected = cx.editor.theme.get("ui.text.focus"); let selected = cx.editor.theme.get("ui.text.focus");
let highlighted = cx.editor.theme.get("special").add_modifier(Modifier::BOLD); let highlighted = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);

View File

@ -1,12 +1,15 @@
use crate::{ use crate::{
compositor::{Callback, Component, Context, EventResult}, compositor::{Callback, Component, Context, EventResult, RenderContext},
ctrl, key, ctrl, key,
}; };
use crossterm::event::Event; use crossterm::event::Event;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use helix_core::Position; use helix_core::Position;
use helix_view::graphics::{Margin, Rect}; use helix_view::{
graphics::{Margin, Rect},
Editor,
};
// TODO: share logic with Menu, it's essentially Popup(render_fn), but render fn needs to return // 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>) // a width/height hint. maybe Popup(Box<Component>)
@ -53,10 +56,10 @@ pub fn auto_close(mut self, auto_close: bool) -> Self {
self self
} }
pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) { pub fn get_rel_position(&mut self, viewport: Rect, editor: &Editor) -> (u16, u16) {
let position = self let position = self
.position .position
.get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default()); .get_or_insert_with(|| editor.cursor().0.unwrap_or_default());
let (width, height) = self.size; let (width, height) = self.size;
@ -176,13 +179,13 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
Some(self.size) Some(self.size)
} }
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
// trigger required_size so we recalculate if the child changed // trigger required_size so we recalculate if the child changed
self.required_size((viewport.width, viewport.height)); self.required_size((viewport.width, viewport.height));
cx.scroll = Some(self.scroll); cx.scroll = Some(self.scroll);
let (rel_x, rel_y) = self.get_rel_position(viewport, cx); let (rel_x, rel_y) = self.get_rel_position(viewport, cx.editor);
// clip to viewport // clip to viewport
let area = viewport.intersection(Rect::new(rel_x, rel_y, self.size.0, self.size.1)); let area = viewport.intersection(Rect::new(rel_x, rel_y, self.size.0, self.size.1));

View File

@ -1,4 +1,4 @@
use crate::compositor::{Component, Compositor, Context, EventResult}; use crate::compositor::{Component, Compositor, Context, EventResult, RenderContext};
use crate::{alt, ctrl, key, shift, ui}; use crate::{alt, ctrl, key, shift, ui};
use crossterm::event::Event; use crossterm::event::Event;
use helix_view::input::KeyEvent; use helix_view::input::KeyEvent;
@ -327,7 +327,7 @@ pub fn exit_selection(&mut self) {
const BASE_WIDTH: u16 = 30; const BASE_WIDTH: u16 = 30;
impl Prompt { impl Prompt {
pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context) { pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
let theme = &cx.editor.theme; let theme = &cx.editor.theme;
let prompt_color = theme.get("ui.text"); let prompt_color = theme.get("ui.text");
let completion_color = theme.get("ui.statusline"); let completion_color = theme.get("ui.statusline");
@ -547,7 +547,7 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut RenderContext<'_>) {
self.render_prompt(area, surface, cx) self.render_prompt(area, surface, cx)
} }

View File

@ -1,4 +1,4 @@
use crate::compositor::{Component, Context}; use crate::compositor::{Component, RenderContext};
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use helix_view::graphics::Rect; use helix_view::graphics::Rect;
@ -30,7 +30,7 @@ fn from(contents: tui::text::Text<'static>) -> Self {
} }
impl Component for Text { impl Component for Text {
fn render(&mut self, area: Rect, surface: &mut Surface, _cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, _cx: &mut RenderContext<'_>) {
use tui::widgets::{Paragraph, Widget, Wrap}; use tui::widgets::{Paragraph, Widget, Wrap};
let par = Paragraph::new(self.contents.clone()).wrap(Wrap { trim: false }); let par = Paragraph::new(self.contents.clone()).wrap(Wrap { trim: false });

View File

@ -2,7 +2,7 @@
use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; use helix_core::{register::Registers, unicode::width::UnicodeWidthStr};
use std::{collections::BTreeSet, fmt::Write}; use std::{collections::BTreeSet, fmt::Write};
#[derive(Debug)] #[derive(Debug, Clone)]
/// Info box used in editor. Rendering logic will be in other crate. /// Info box used in editor. Rendering logic will be in other crate.
pub struct Info { pub struct Info {
/// Title shown at top. /// Title shown at top.