diff --git a/CHANGELOG.md b/CHANGELOG.md index a991338..48ca1e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/buffer.rs b/src/buffer.rs index 369f861..e4aa575 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -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(&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); } } } diff --git a/src/edit/editor.rs b/src/edit/editor.rs index 69d4230..b290ae8 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -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( + &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); } } }); diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index d348a83..cf820ee 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -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(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F) + pub fn draw(&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(&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, ); } } diff --git a/src/edit/vi.rs b/src/edit/vi.rs index 165523e..aca3468 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -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(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F) + pub fn draw(&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(&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); } } }); diff --git a/src/lib.rs b/src/lib.rs index 1ce9f14..0cad691 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..8db935f --- /dev/null +++ b/src/render.rs @@ -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, + ); + }, + ); + } +}