Add Renderer trait for more flexible rendering of buffers and editors
This commit is contained in:
parent
8a7bc790e5
commit
9339446cfa
7 changed files with 135 additions and 79 deletions
|
|
@ -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/),
|
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).
|
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
|
## [0.15.0] - 2025-10-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,10 @@ use core::{cmp, fmt};
|
||||||
use core_maths::CoreFloat;
|
use core_maths::CoreFloat;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
use crate::Color;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Cursor,
|
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color,
|
||||||
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Scroll,
|
Cursor, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion,
|
||||||
ShapeLine, Shaping, Wrap,
|
Renderer, Scroll, ShapeLine, Shaping, Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A line of visible text for rendering
|
/// A line of visible text for rendering
|
||||||
|
|
@ -1350,29 +1347,24 @@ impl Buffer {
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
cache: &mut crate::SwashCache,
|
cache: &mut crate::SwashCache,
|
||||||
color: Color,
|
color: Color,
|
||||||
mut f: F,
|
callback: F,
|
||||||
) where
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
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 run in self.layout_runs() {
|
||||||
for glyph in run.glyphs {
|
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);
|
let glyph_color = glyph.color_opt.map_or(color, |some| some);
|
||||||
|
renderer.glyph(physical_glyph, glyph_color);
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,13 @@ use alloc::{
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
use core::cmp;
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
use std::cmp;
|
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
use crate::Color;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem,
|
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
|
/// A wrapper of [`Buffer`] for easy editing
|
||||||
|
|
@ -115,10 +111,32 @@ impl<'buffer> Editor<'buffer> {
|
||||||
cursor_color: Color,
|
cursor_color: Color,
|
||||||
selection_color: Color,
|
selection_color: Color,
|
||||||
selected_text_color: Color,
|
selected_text_color: Color,
|
||||||
mut f: F,
|
callback: F,
|
||||||
) where
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
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();
|
let selection_bounds = self.selection_bounds();
|
||||||
self.with_buffer(|buffer| {
|
self.with_buffer(|buffer| {
|
||||||
for run in buffer.layout_runs() {
|
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)),
|
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||||
};
|
};
|
||||||
} else if let Some((min, max)) = range_opt.take() {
|
} else if let Some((min, max)) = range_opt.take() {
|
||||||
f(
|
renderer.rectangle(
|
||||||
min,
|
min,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
cmp::max(0, max - min) as u32,
|
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;
|
max = buffer.size().0.unwrap_or(0.0) as i32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(
|
renderer.rectangle(
|
||||||
min,
|
min,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
|
|
@ -190,11 +208,11 @@ impl<'buffer> Editor<'buffer> {
|
||||||
|
|
||||||
// Draw cursor
|
// Draw cursor
|
||||||
if let Some((x, y)) = cursor_position(&self.cursor, &run) {
|
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 {
|
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);
|
let mut glyph_color = glyph.color_opt.map_or(text_color, |some| some);
|
||||||
if text_color != selected_text_color {
|
if text_color != selected_text_color {
|
||||||
|
|
@ -209,20 +227,7 @@ impl<'buffer> Editor<'buffer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.with_pixels(
|
renderer.glyph(physical_glyph, glyph_color);
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, Editor,
|
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;
|
pub use syntect::highlighting::Theme as SyntaxTheme;
|
||||||
|
|
@ -219,24 +219,31 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
||||||
|
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
#[cfg(feature = "swash")]
|
#[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
|
where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
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());
|
let size = self.with_buffer(|buffer| buffer.size());
|
||||||
if let Some(width) = size.0 {
|
if let Some(width) = size.0 {
|
||||||
if let Some(height) = size.1 {
|
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(
|
self.editor.render(
|
||||||
font_system,
|
renderer,
|
||||||
cache,
|
|
||||||
self.foreground_color(),
|
self.foreground_color(),
|
||||||
self.cursor_color(),
|
self.cursor_color(),
|
||||||
self.selection_color(),
|
self.selection_color(),
|
||||||
self.foreground_color(),
|
self.foreground_color(),
|
||||||
f,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
use alloc::{collections::BTreeMap, string::String};
|
use alloc::{collections::BTreeMap, string::String};
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
use modit::{Event, Key, Parser, TextObject, WordIter};
|
use modit::{Event, Key, Parser, TextObject, WordIter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, FontSystem,
|
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, FontSystem,
|
||||||
Motion, Selection, SyntaxEditor, SyntaxTheme,
|
Motion, Renderer, Selection, SyntaxEditor, SyntaxTheme,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use modit::{ViMode, ViParser};
|
pub use modit::{ViMode, ViParser};
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
fn undo_2_action<'buffer, E: Edit<'buffer>>(
|
fn undo_2_action<'buffer, E: Edit<'buffer>>(
|
||||||
|
|
@ -305,10 +303,19 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
#[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
|
where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
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 background_color = self.background_color();
|
||||||
let foreground_color = self.foreground_color();
|
let foreground_color = self.foreground_color();
|
||||||
let cursor_color = self.cursor_color();
|
let cursor_color = self.cursor_color();
|
||||||
|
|
@ -317,7 +324,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
let size = buffer.size();
|
let size = buffer.size();
|
||||||
if let Some(width) = size.0 {
|
if let Some(width) = size.0 {
|
||||||
if let Some(height) = size.1 {
|
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;
|
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)),
|
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||||
};
|
};
|
||||||
} else if let Some((min, max)) = range_opt.take() {
|
} else if let Some((min, max)) = range_opt.take() {
|
||||||
f(
|
renderer.rectangle(
|
||||||
min,
|
min,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
cmp::max(0, max - min) as u32,
|
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;
|
max = buffer.size().0.unwrap_or(0.0) as i32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(
|
renderer.rectangle(
|
||||||
min,
|
min,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
|
|
@ -476,7 +483,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
if block_cursor {
|
if block_cursor {
|
||||||
let left_x = cmp::min(start_x, end_x);
|
let left_x = cmp::min(start_x, end_x);
|
||||||
let right_x = cmp::max(start_x, end_x);
|
let right_x = cmp::max(start_x, end_x);
|
||||||
f(
|
renderer.rectangle(
|
||||||
left_x,
|
left_x,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
(right_x - left_x) as u32,
|
(right_x - left_x) as u32,
|
||||||
|
|
@ -484,7 +491,7 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
selection_color,
|
selection_color,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
f(
|
renderer.rectangle(
|
||||||
start_x,
|
start_x,
|
||||||
line_top as i32,
|
line_top as i32,
|
||||||
1,
|
1,
|
||||||
|
|
@ -495,27 +502,14 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for glyph in run.glyphs.iter() {
|
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 {
|
let glyph_color = match glyph.color_opt {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => foreground_color,
|
None => foreground_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
cache.with_pixels(
|
renderer.glyph(physical_glyph, glyph_color);
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,9 @@ mod layout;
|
||||||
pub use self::line_ending::*;
|
pub use self::line_ending::*;
|
||||||
mod line_ending;
|
mod line_ending;
|
||||||
|
|
||||||
|
pub use self::render::*;
|
||||||
|
mod render;
|
||||||
|
|
||||||
pub use self::shape::*;
|
pub use self::shape::*;
|
||||||
mod shape;
|
mod shape;
|
||||||
|
|
||||||
|
|
|
||||||
49
src/render.rs
Normal file
49
src/render.rs
Normal 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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue