Add Renderer trait for more flexible rendering of buffers and editors

This commit is contained in:
Jeremy Soller 2025-11-06 11:23:11 -07:00
parent 8a7bc790e5
commit 9339446cfa
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
7 changed files with 135 additions and 79 deletions

View file

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Add `Renderer` trait for more flexible rendering of buffers and editors
## [0.15.0] - 2025-10-30
### Added

View file

@ -9,13 +9,10 @@ use core::{cmp, fmt};
use core_maths::CoreFloat;
use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Cursor,
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Scroll,
ShapeLine, Shaping, Wrap,
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color,
Cursor, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion,
Renderer, Scroll, ShapeLine, Shaping, Wrap,
};
/// A line of visible text for rendering
@ -1350,29 +1347,24 @@ impl Buffer {
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
callback: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let mut renderer = crate::LegacyRenderer {
font_system,
cache,
callback,
};
self.render(&mut renderer, color);
}
pub fn render<R: Renderer>(&self, renderer: &mut R, color: Color) {
for run in self.layout_runs() {
for glyph in run.glyphs {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
let glyph_color = glyph.color_opt.map_or(color, |some| some);
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
run.line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
renderer.glyph(physical_glyph, glyph_color);
}
}
}

View file

@ -5,17 +5,13 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "swash")]
use std::cmp;
use core::cmp;
use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{
Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem,
Cursor, Edit, FontSystem, LayoutRun, LineEnding, LineIter, Selection, Shaping,
Color, Cursor, Edit, FontSystem, LayoutRun, LineEnding, LineIter, Renderer, Selection, Shaping,
};
/// A wrapper of [`Buffer`] for easy editing
@ -115,10 +111,32 @@ impl<'buffer> Editor<'buffer> {
cursor_color: Color,
selection_color: Color,
selected_text_color: Color,
mut f: F,
callback: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let mut renderer = crate::LegacyRenderer {
font_system,
cache,
callback,
};
self.render(
&mut renderer,
text_color,
cursor_color,
selection_color,
selected_text_color,
);
}
pub fn render<R: Renderer>(
&self,
renderer: &mut R,
text_color: Color,
cursor_color: Color,
selection_color: Color,
selected_text_color: Color,
) {
let selection_bounds = self.selection_bounds();
self.with_buffer(|buffer| {
for run in buffer.layout_runs() {
@ -151,7 +169,7 @@ impl<'buffer> Editor<'buffer> {
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
renderer.rectangle(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
@ -177,7 +195,7 @@ impl<'buffer> Editor<'buffer> {
max = buffer.size().0.unwrap_or(0.0) as i32;
}
}
f(
renderer.rectangle(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
@ -190,11 +208,11 @@ impl<'buffer> Editor<'buffer> {
// Draw cursor
if let Some((x, y)) = cursor_position(&self.cursor, &run) {
f(x, y, 1, line_height as u32, cursor_color);
renderer.rectangle(x, y, 1, line_height as u32, cursor_color);
}
for glyph in run.glyphs {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let physical_glyph = glyph.physical((0., line_y), 1.0);
let mut glyph_color = glyph.color_opt.map_or(text_color, |some| some);
if text_color != selected_text_color {
@ -209,20 +227,7 @@ impl<'buffer> Editor<'buffer> {
}
}
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
renderer.glyph(physical_glyph, glyph_color);
}
}
});

View file

@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, Editor,
FontSystem, Selection, Shaping, Style, Weight,
FontSystem, Renderer, Selection, Shaping, Style, Weight,
};
pub use syntect::highlighting::Theme as SyntaxTheme;
@ -219,24 +219,31 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let mut renderer = crate::LegacyRenderer {
font_system,
cache,
callback,
};
self.render(&mut renderer);
}
pub fn render<R: Renderer>(&self, renderer: &mut R) {
let size = self.with_buffer(|buffer| buffer.size());
if let Some(width) = size.0 {
if let Some(height) = size.1 {
f(0, 0, width as u32, height as u32, self.background_color());
renderer.rectangle(0, 0, width as u32, height as u32, self.background_color());
}
}
self.editor.draw(
font_system,
cache,
self.editor.render(
renderer,
self.foreground_color(),
self.cursor_color(),
self.selection_color(),
self.foreground_color(),
f,
);
}
}

View file

@ -1,15 +1,13 @@
use alloc::{collections::BTreeMap, string::String};
#[cfg(feature = "swash")]
use core::cmp;
use modit::{Event, Key, Parser, TextObject, WordIter};
use crate::{
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, FontSystem,
Motion, Selection, SyntaxEditor, SyntaxTheme,
Motion, Renderer, Selection, SyntaxEditor, SyntaxTheme,
};
pub use modit::{ViMode, ViParser};
#[cfg(feature = "swash")]
use unicode_segmentation::UnicodeSegmentation;
fn undo_2_action<'buffer, E: Edit<'buffer>>(
@ -305,10 +303,19 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
}
#[cfg(feature = "swash")]
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let mut renderer = crate::LegacyRenderer {
font_system,
cache,
callback,
};
self.render(&mut renderer);
}
pub fn render<R: Renderer>(&self, renderer: &mut R) {
let background_color = self.background_color();
let foreground_color = self.foreground_color();
let cursor_color = self.cursor_color();
@ -317,7 +324,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
let size = buffer.size();
if let Some(width) = size.0 {
if let Some(height) = size.1 {
f(0, 0, width as u32, height as u32, background_color);
renderer.rectangle(0, 0, width as u32, height as u32, background_color);
}
}
let font_size = buffer.metrics().font_size;
@ -388,7 +395,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
renderer.rectangle(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
@ -414,7 +421,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
max = buffer.size().0.unwrap_or(0.0) as i32;
}
}
f(
renderer.rectangle(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
@ -476,7 +483,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
if block_cursor {
let left_x = cmp::min(start_x, end_x);
let right_x = cmp::max(start_x, end_x);
f(
renderer.rectangle(
left_x,
line_top as i32,
(right_x - left_x) as u32,
@ -484,7 +491,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
selection_color,
);
} else {
f(
renderer.rectangle(
start_x,
line_top as i32,
1,
@ -495,27 +502,14 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let physical_glyph = glyph.physical((0., line_y), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => foreground_color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
renderer.glyph(physical_glyph, glyph_color);
}
}
});

View file

@ -129,6 +129,9 @@ mod layout;
pub use self::line_ending::*;
mod line_ending;
pub use self::render::*;
mod render;
pub use self::shape::*;
mod shape;

49
src/render.rs Normal file
View file

@ -0,0 +1,49 @@
//! Helpers for rendering buffers and editors
use crate::{Color, PhysicalGlyph};
#[cfg(feature = "swash")]
use crate::{FontSystem, SwashCache};
/// Custom renderer for buffers and editors
pub trait Renderer {
/// Render a rectangle at x, y with size w, h and the provided [`Color`].
fn rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color);
/// Render a [`PhysicalGlyph`] with the provided [`Color`].
/// For performance, consider using [`SwashCache`].
fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color);
}
/// Helper to migrate from old renderer
//TODO: remove in future version
#[cfg(feature = "swash")]
#[derive(Debug)]
pub struct LegacyRenderer<'a, F: FnMut(i32, i32, u32, u32, Color)> {
pub font_system: &'a mut FontSystem,
pub cache: &'a mut SwashCache,
pub callback: F,
}
#[cfg(feature = "swash")]
impl<'a, F: FnMut(i32, i32, u32, u32, Color)> Renderer for LegacyRenderer<'a, F> {
fn rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
(self.callback)(x, y, w, h, color);
}
fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color) {
self.cache.with_pixels(
self.font_system,
physical_glyph.cache_key,
color,
|x, y, pixel_color| {
(self.callback)(
physical_glyph.x + x,
physical_glyph.y + y,
1,
1,
pixel_color,
);
},
);
}
}