diff --git a/examples/editor-libcosmic/Cargo.toml b/examples/editor-libcosmic/Cargo.toml index 489cb72..82e2ec0 100644 --- a/examples/editor-libcosmic/Cargo.toml +++ b/examples/editor-libcosmic/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" publish = false [dependencies] -cosmic-text = { path = "../../", features = ["syntect"] } +cosmic-text = { path = "../../", features = ["syntect", "vi"] } env_logger = "0.10" fontdb = "0.13" lazy_static = "1.4" @@ -26,4 +26,4 @@ version = "0.11" [features] default = [] -vi = ["cosmic-text/vi"] +vi = [] diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 18ee393..0db3e72 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -86,9 +86,6 @@ pub struct Window { path_opt: Option, attrs: Attrs<'static>, font_size: FontSize, - #[cfg(not(feature = "vi"))] - editor: Mutex>, - #[cfg(feature = "vi")] editor: Mutex>, } @@ -133,7 +130,7 @@ impl Application for Window { fn new(_flags: ()) -> (Self, Command) { let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace); - let mut editor = SyntaxEditor::new( + let editor = SyntaxEditor::new( Buffer::new( &mut FONT_SYSTEM.lock().unwrap(), FontSize::Body.to_metrics(), @@ -143,8 +140,8 @@ impl Application for Window { ) .unwrap(); - #[cfg(feature = "vi")] let mut editor = cosmic_text::ViEditor::new(editor); + editor.set_passthrough(cfg!(feature = "vi")); update_attrs(&mut editor, attrs); diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index 7f65558..8234d4c 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -5,14 +5,13 @@ use cosmic::{ iced_runtime::keyboard::KeyCode, theme::{Theme, ThemeType}, }; -use cosmic_text::{Action, Edit, Motion, SwashCache}; +use cosmic_text::{Action, Edit, Motion, SwashCache, ViEditor}; use std::{cmp, sync::Mutex, time::Instant}; use crate::FONT_SYSTEM; pub struct Appearance { background_color: Option, - text_color: Color, } pub trait StyleSheet { @@ -24,23 +23,21 @@ impl StyleSheet for Theme { match self.theme_type { ThemeType::Dark | ThemeType::HighContrastDark | ThemeType::Custom(_) => Appearance { background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)), - text_color: Color::from_rgb8(0xFF, 0xFF, 0xFF), }, ThemeType::Light | ThemeType::HighContrastLight => Appearance { background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)), - text_color: Color::from_rgb8(0x00, 0x00, 0x00), }, } } } -pub struct TextBox<'a, Editor> { - editor: &'a Mutex, +pub struct TextBox<'a, 'editor> { + editor: &'a Mutex>, padding: Padding, } -impl<'a, Editor> TextBox<'a, Editor> { - pub fn new(editor: &'a Mutex) -> Self { +impl<'a, 'editor> TextBox<'a, 'editor> { + pub fn new(editor: &'a Mutex>) -> Self { Self { editor, padding: Padding::new(0.), @@ -53,7 +50,7 @@ impl<'a, Editor> TextBox<'a, Editor> { } } -pub fn text_box<'a, Editor>(editor: &'a Mutex) -> TextBox<'a, Editor> { +pub fn text_box<'a, 'editor>(editor: &'a Mutex>) -> TextBox<'a, 'editor> { TextBox::new(editor) } @@ -106,11 +103,10 @@ fn draw_pixel( buffer[offset + 3] = (current >> 24) as u8; } -impl<'a, 'editor, Editor, Message, Renderer> Widget for TextBox<'a, Editor> +impl<'a, 'editor, Message, Renderer> Widget for TextBox<'a, 'editor> where Renderer: cosmic::iced_core::Renderer + image::Renderer, Renderer::Theme: StyleSheet, - Editor: Edit, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -193,13 +189,6 @@ where ); } - let text_color = cosmic_text::Color::rgba( - cmp::max(0, cmp::min(255, (appearance.text_color.r * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.g * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.b * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.a * 255.0) as i32)) as u8, - ); - let mut editor = self.editor.lock().unwrap(); let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) @@ -229,18 +218,14 @@ where // Draw to pixel buffer let mut pixels = vec![0; image_w as usize * image_h as usize * 4]; - editor.draw( - &mut state.cache.lock().unwrap(), - text_color, - |x, y, w, h, color| { - //TODO: improve performance - for row in 0..h as i32 { - for col in 0..w as i32 { - draw_pixel(&mut pixels, image_w, image_h, x + col, y + row, color); - } + editor.draw(&mut state.cache.lock().unwrap(), |x, y, w, h, color| { + //TODO: improve performance + for row in 0..h as i32 { + for col in 0..w as i32 { + draw_pixel(&mut pixels, image_w, image_h, x + col, y + row, color); } - }, - ); + } + }); // Restore original metrics editor.buffer_mut().set_metrics(metrics); @@ -370,14 +355,12 @@ where } } -impl<'a, 'editor, Editor, Message, Renderer> From> - for Element<'a, Message, Renderer> +impl<'a, 'editor, Message, Renderer> From> for Element<'a, Message, Renderer> where Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet, - Editor: Edit, { - fn from(text_box: TextBox<'a, Editor>) -> Self { + fn from(text_box: TextBox<'a, 'editor>) -> Self { Self::new(text_box) } } diff --git a/examples/editor-orbclient/src/main.rs b/examples/editor-orbclient/src/main.rs index 53f9116..21652f2 100644 --- a/examples/editor-orbclient/src/main.rs +++ b/examples/editor-orbclient/src/main.rs @@ -95,8 +95,7 @@ fn main() { let bg = editor.background_color(); window.set(orbclient::Color::rgb(bg.r(), bg.g(), bg.b())); - let fg = editor.foreground_color(); - editor.draw(&mut swash_cache, fg, |x, y, w, h, color| { + editor.draw(&mut swash_cache, |x, y, w, h, color| { window.rect( line_x as i32 + x, y, diff --git a/examples/editor-test/src/main.rs b/examples/editor-test/src/main.rs index 0d95ed2..8ac8d6f 100644 --- a/examples/editor-test/src/main.rs +++ b/examples/editor-test/src/main.rs @@ -15,6 +15,8 @@ fn redraw( ) { let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34); let font_color = Color::rgb(0xFF, 0xFF, 0xFF); + let cursor_color = Color::rgb(0xFF, 0xFF, 0xFF); + let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33); editor.shape_as_needed(true); if editor.buffer().redraw() { @@ -22,9 +24,15 @@ fn redraw( window.set(bg_color); - editor.draw(swash_cache, font_color, |x, y, w, h, color| { - window.rect(x, y, w, h, orbclient::Color { data: color.0 }); - }); + editor.draw( + swash_cache, + font_color, + cursor_color, + selection_color, + |x, y, w, h, color| { + window.rect(x, y, w, h, orbclient::Color { data: color.0 }); + }, + ); window.sync(); diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index 78d0774..8da5168 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -130,6 +130,8 @@ fn main() { loop { let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34); let font_color = Color::rgb(0xFF, 0xFF, 0xFF); + let cursor_color = Color::rgb(0xFF, 0xFF, 0xFF); + let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33); editor.shape_as_needed(true); if editor.buffer().redraw() { @@ -137,9 +139,15 @@ fn main() { window.set(bg_color); - editor.draw(&mut swash_cache, font_color, |x, y, w, h, color| { - window.rect(x, y, w, h, orbclient::Color { data: color.0 }); - }); + editor.draw( + &mut swash_cache, + font_color, + cursor_color, + selection_color, + |x, y, w, h, color| { + window.rect(x, y, w, h, orbclient::Color { data: color.0 }); + }, + ); window.sync(); diff --git a/src/buffer.rs b/src/buffer.rs index 84fa34e..75678aa 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -365,10 +365,9 @@ impl Buffer { while self.scroll.layout < 0 { if self.scroll.line > 0 { self.scroll.line -= 1; - let layout = self - .line_layout(font_system, self.scroll.line) - .expect("shape_until_scroll invalid scroll.line"); - self.scroll.layout += layout.len() as i32; + if let Some(layout) = self.line_layout(font_system, self.scroll.line) { + self.scroll.layout += layout.len() as i32; + } } else { self.scroll.layout = 0; break; @@ -801,8 +800,9 @@ impl Buffer { &mut self, font_system: &mut FontSystem, mut cursor: Cursor, + mut cursor_x_opt: Option, motion: Motion, - ) -> Option { + ) -> Option<(Cursor, Option)> { match motion { Motion::LayoutCursor(layout_cursor) => { let layout = self.line_layout(font_system, layout_cursor.line)?; @@ -811,7 +811,9 @@ impl Buffer { Some(some) => some, None => match layout.last() { Some(some) => some, - None => return None, + None => { + return None; + } }, }; @@ -853,7 +855,7 @@ impl Buffer { cursor.index = self.lines.get(cursor.line)?.text().len(); cursor.affinity = Affinity::After; } - cursor.x_opt = None; + cursor_x_opt = None; } Motion::Next => { let line = self.lines.get(cursor.line)?; @@ -870,7 +872,7 @@ impl Buffer { cursor.index = 0; cursor.affinity = Affinity::Before; } - cursor.x_opt = None; + cursor_x_opt = None; } Motion::Left => { let rtl_opt = self @@ -878,9 +880,15 @@ impl Buffer { .map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { - cursor = self.cursor_motion(font_system, cursor, Motion::Next)?; + (cursor, cursor_x_opt) = + self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?; } else { - cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::Previous, + )?; } } } @@ -890,17 +898,23 @@ impl Buffer { .map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { - cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::Previous, + )?; } else { - cursor = self.cursor_motion(font_system, cursor, Motion::Next)?; + (cursor, cursor_x_opt) = + self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?; } } } Motion::Up => { let mut layout_cursor = self.layout_cursor(font_system, cursor)?; - if cursor.x_opt.is_none() { - cursor.x_opt = Some( + if cursor_x_opt.is_none() { + cursor_x_opt = Some( layout_cursor.glyph as i32, //TODO: glyph x position ); } @@ -912,20 +926,24 @@ impl Buffer { layout_cursor.layout = usize::max_value(); } - if let Some(cursor_x) = cursor.x_opt { + if let Some(cursor_x) = cursor_x_opt { layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position } - cursor = - self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::LayoutCursor(layout_cursor), + )?; } Motion::Down => { let mut layout_cursor = self.layout_cursor(font_system, cursor)?; let layout_len = self.line_layout(font_system, layout_cursor.line)?.len(); - if cursor.x_opt.is_none() { - cursor.x_opt = Some( + if cursor_x_opt.is_none() { + cursor_x_opt = Some( layout_cursor.glyph as i32, //TODO: glyph x position ); } @@ -937,19 +955,30 @@ impl Buffer { layout_cursor.layout = 0; } - if let Some(cursor_x) = cursor.x_opt { + if let Some(cursor_x) = cursor_x_opt { layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position } - cursor = - self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::LayoutCursor(layout_cursor), + )?; } Motion::Home => { let mut layout_cursor = self.layout_cursor(font_system, cursor)?; layout_cursor.glyph = 0; - cursor = - self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?; - cursor.x_opt = None; + #[allow(unused_assignments)] + { + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::LayoutCursor(layout_cursor), + )?; + } + cursor_x_opt = None; } Motion::SoftHome => { let line = self.lines.get(cursor.line)?; @@ -959,34 +988,43 @@ impl Buffer { .filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) }) .next() .unwrap_or(0); - cursor.x_opt = None; + cursor_x_opt = None; } Motion::End => { let mut layout_cursor = self.layout_cursor(font_system, cursor)?; layout_cursor.glyph = usize::max_value(); - cursor = - self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?; - cursor.x_opt = None; + #[allow(unused_assignments)] + { + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::LayoutCursor(layout_cursor), + )?; + } + cursor_x_opt = None; } Motion::ParagraphStart => { cursor.index = 0; - cursor.x_opt = None; + cursor_x_opt = None; } Motion::ParagraphEnd => { cursor.index = self.lines.get(cursor.line)?.text().len(); - cursor.x_opt = None; + cursor_x_opt = None; } Motion::PageUp => { - cursor = self.cursor_motion( + (cursor, cursor_x_opt) = self.cursor_motion( font_system, cursor, + cursor_x_opt, Motion::Vertical(-self.size().1 as i32), )?; } Motion::PageDown => { - cursor = self.cursor_motion( + (cursor, cursor_x_opt) = self.cursor_motion( font_system, cursor, + cursor_x_opt, Motion::Vertical(self.size().1 as i32), )?; } @@ -996,12 +1034,18 @@ impl Buffer { match lines.cmp(&0) { cmp::Ordering::Less => { for _ in 0..-lines { - cursor = self.cursor_motion(font_system, cursor, Motion::Up)?; + (cursor, cursor_x_opt) = + self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Up)?; } } cmp::Ordering::Greater => { for _ in 0..lines { - cursor = self.cursor_motion(font_system, cursor, Motion::Down)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::Down, + )?; } } cmp::Ordering::Equal => {} @@ -1021,7 +1065,7 @@ impl Buffer { cursor.line -= 1; cursor.index = self.lines.get(cursor.line)?.text().len(); } - cursor.x_opt = None; + cursor_x_opt = None; } Motion::NextWord => { let line = self.lines.get(cursor.line)?; @@ -1036,7 +1080,7 @@ impl Buffer { cursor.line += 1; cursor.index = 0; } - cursor.x_opt = None; + cursor_x_opt = None; } Motion::LeftWord => { let rtl_opt = self @@ -1044,9 +1088,19 @@ impl Buffer { .map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { - cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::NextWord, + )?; } else { - cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::PreviousWord, + )?; } } } @@ -1056,30 +1110,44 @@ impl Buffer { .map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { - cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::PreviousWord, + )?; } else { - cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::NextWord, + )?; } } } Motion::BufferStart => { cursor.line = 0; cursor.index = 0; - cursor.x_opt = None; + cursor_x_opt = None; } Motion::BufferEnd => { cursor.line = self.lines.len() - 1; cursor.index = self.lines.get(cursor.line)?.text().len(); - cursor.x_opt = None; + cursor_x_opt = None; } Motion::GotoLine(line) => { let mut layout_cursor = self.layout_cursor(font_system, cursor)?; layout_cursor.line = line; - cursor = - self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?; + (cursor, cursor_x_opt) = self.cursor_motion( + font_system, + cursor, + cursor_x_opt, + Motion::LayoutCursor(layout_cursor), + )?; } } - Some(cursor) + Some((cursor, cursor_x_opt)) } /// Draw the buffer @@ -1203,8 +1271,14 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> { } /// Apply a [`Motion`] to a [`Cursor`] - pub fn cursor_motion(&mut self, cursor: Cursor, motion: Motion) -> Option { - self.inner.cursor_motion(self.font_system, cursor, motion) + pub fn cursor_motion( + &mut self, + cursor: Cursor, + cursor_x_opt: Option, + motion: Motion, + ) -> Option<(Cursor, Option)> { + self.inner + .cursor_motion(self.font_system, cursor, cursor_x_opt, motion) } /// Draw the buffer diff --git a/src/cursor.rs b/src/cursor.rs index fe44c8d..1bff075 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,5 +1,3 @@ -use crate::Color; - /// Current cursor location #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct Cursor { @@ -10,10 +8,6 @@ pub struct Cursor { /// Whether to associate the cursor with the run before it or the run after it if placed at the /// boundary between two runs pub affinity: Affinity, - /// Cached X position used for up and down movement - pub x_opt: Option, - /// Cursor color - pub color: Option, } impl Cursor { @@ -28,18 +22,6 @@ impl Cursor { line, index, affinity, - x_opt: None, - color: None, - } - } - /// Create a new cursor, specifying the color - pub const fn new_with_color(line: usize, index: usize, color: Color) -> Self { - Self { - line, - index, - affinity: Affinity::Before, - x_opt: None, - color: Some(color), } } } @@ -79,7 +61,7 @@ impl Affinity { } /// The position of a cursor within a [`Buffer`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct LayoutCursor { /// Index of [`BufferLine`] in [`Buffer::lines`] pub line: usize, @@ -150,7 +132,7 @@ pub enum Motion { } /// Scroll position in [`Buffer`] -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct Scroll { /// Index of [`BufferLine`] in [`Buffer::lines`]. This will be adjusted as needed if layout is /// out of bounds diff --git a/src/edit/editor.rs b/src/edit/editor.rs index fb670d4..343c7c0 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -8,8 +8,8 @@ use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "swash")] use crate::Color; use crate::{ - Action, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, Selection, - Shaping, + Action, AttrsList, BorrowedWithFontSystem, Buffer, BufferLine, Change, ChangeItem, Cursor, + Edit, FontSystem, Selection, Shaping, }; /// A wrapper of [`Buffer`] for easy editing @@ -17,6 +17,7 @@ use crate::{ pub struct Editor { buffer: Buffer, cursor: Cursor, + cursor_x_opt: Option, selection: Selection, cursor_moved: bool, auto_indent: bool, @@ -30,6 +31,7 @@ impl Editor { Self { buffer, cursor: Cursor::default(), + cursor_x_opt: None, selection: Selection::None, cursor_moved: false, auto_indent: false, @@ -37,6 +39,179 @@ impl Editor { change: None, } } + + /// Draw the editor + #[cfg(feature = "swash")] + pub fn draw( + &self, + font_system: &mut FontSystem, + cache: &mut crate::SwashCache, + text_color: Color, + cursor_color: Color, + selection_color: Color, + mut f: F, + ) where + F: FnMut(i32, i32, u32, u32, Color), + { + let line_height = self.buffer.metrics().line_height; + + for run in self.buffer.layout_runs() { + let line_i = run.line_i; + let line_y = run.line_y; + let line_top = run.line_top; + + let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> { + if cursor.line == line_i { + for (glyph_i, glyph) in run.glyphs.iter().enumerate() { + if cursor.index == glyph.start { + return Some((glyph_i, 0.0)); + } else if cursor.index > glyph.start && cursor.index < glyph.end { + // Guess x offset based on characters + let mut before = 0; + let mut total = 0; + + let cluster = &run.text[glyph.start..glyph.end]; + for (i, _) in cluster.grapheme_indices(true) { + if glyph.start + i < cursor.index { + before += 1; + } + total += 1; + } + + let offset = glyph.w * (before as f32) / (total as f32); + return Some((glyph_i, offset)); + } + } + match run.glyphs.last() { + Some(glyph) => { + if cursor.index == glyph.end { + return Some((run.glyphs.len(), 0.0)); + } + } + None => { + return Some((0, 0.0)); + } + } + } + None + }; + + // Highlight selection (TODO: HIGHLIGHT COLOR!) + if let Some((start, end)) = self.selection_bounds() { + if line_i >= start.line && line_i <= end.line { + let mut range_opt = None; + for glyph in run.glyphs.iter() { + // Guess x offset based on characters + let cluster = &run.text[glyph.start..glyph.end]; + let total = cluster.grapheme_indices(true).count(); + let mut c_x = glyph.x; + let c_w = glyph.w / total as f32; + for (i, c) in cluster.grapheme_indices(true) { + let c_start = glyph.start + i; + let c_end = glyph.start + i + c.len(); + if (start.line != line_i || c_end > start.index) + && (end.line != line_i || c_start < end.index) + { + range_opt = match range_opt.take() { + Some((min, max)) => Some(( + cmp::min(min, c_x as i32), + cmp::max(max, (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() { + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + c_x += c_w; + } + } + + if run.glyphs.is_empty() && end.line > line_i { + // Highlight all of internal empty lines + range_opt = Some((0, self.buffer.size().0 as i32)); + } + + if let Some((mut min, mut max)) = range_opt.take() { + if end.line > line_i { + // Draw to end of line + if run.rtl { + min = 0; + } else { + max = self.buffer.size().0 as i32; + } + } + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + } + } + + // Draw cursor + if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) { + let x = match run.glyphs.get(cursor_glyph) { + Some(glyph) => { + // Start of detected glyph + if glyph.level.is_rtl() { + (glyph.x + glyph.w - cursor_glyph_offset) as i32 + } else { + (glyph.x + cursor_glyph_offset) as i32 + } + } + None => match run.glyphs.last() { + Some(glyph) => { + // End of last glyph + if glyph.level.is_rtl() { + glyph.x as i32 + } else { + (glyph.x + glyph.w) as i32 + } + } + None => { + // Start of empty line + 0 + } + }, + }; + + f(x, line_top as i32, 1, line_height as u32, cursor_color); + } + + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0., 0.), 1.0); + + let glyph_color = match glyph.color_opt { + Some(some) => some, + None => text_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, + ); + }, + ); + } + } + } } impl Edit for Editor { @@ -342,10 +517,12 @@ impl Edit for Editor { match action { Action::Motion(motion) => { - if let Some(new_cursor) = - self.buffer.cursor_motion(font_system, self.cursor, motion) + if let Some((cursor, cursor_x_opt)) = + self.buffer + .cursor_motion(font_system, self.cursor, self.cursor_x_opt, motion) { - self.cursor = new_cursor; + self.cursor = cursor; + self.cursor_x_opt = cursor_x_opt; } } Action::Escape => { @@ -577,9 +754,7 @@ impl Edit for Editor { if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { if new_cursor != self.cursor { - let color = self.cursor.color; self.cursor = new_cursor; - self.cursor.color = color; self.buffer.set_redraw(true); } } @@ -589,9 +764,7 @@ impl Edit for Editor { if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { if new_cursor != self.cursor { - let color = self.cursor.color; self.cursor = new_cursor; - self.cursor.color = color; self.buffer.set_redraw(true); } self.selection = Selection::Word(self.cursor); @@ -603,9 +776,7 @@ impl Edit for Editor { if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { if new_cursor != self.cursor { - let color = self.cursor.color; self.cursor = new_cursor; - self.cursor.color = color; } self.selection = Selection::Line(self.cursor); self.buffer.set_redraw(true); @@ -619,9 +790,7 @@ impl Edit for Editor { if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { if new_cursor != self.cursor { - let color = self.cursor.color; self.cursor = new_cursor; - self.cursor.color = color; self.buffer.set_redraw(true); } } @@ -654,181 +823,27 @@ impl Edit for Editor { */ } } +} - /// Draw the editor +impl<'a> BorrowedWithFontSystem<'a, Editor> { #[cfg(feature = "swash")] - fn draw( - &self, - font_system: &mut FontSystem, + pub fn draw( + &mut self, cache: &mut crate::SwashCache, - color: Color, - mut f: F, + text_color: Color, + cursor_color: Color, + selection_color: Color, + f: F, ) where F: FnMut(i32, i32, u32, u32, Color), { - let line_height = self.buffer.metrics().line_height; - - for run in self.buffer.layout_runs() { - let line_i = run.line_i; - let line_y = run.line_y; - let line_top = run.line_top; - - let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> { - if cursor.line == line_i { - for (glyph_i, glyph) in run.glyphs.iter().enumerate() { - if cursor.index == glyph.start { - return Some((glyph_i, 0.0)); - } else if cursor.index > glyph.start && cursor.index < glyph.end { - // Guess x offset based on characters - let mut before = 0; - let mut total = 0; - - let cluster = &run.text[glyph.start..glyph.end]; - for (i, _) in cluster.grapheme_indices(true) { - if glyph.start + i < cursor.index { - before += 1; - } - total += 1; - } - - let offset = glyph.w * (before as f32) / (total as f32); - return Some((glyph_i, offset)); - } - } - match run.glyphs.last() { - Some(glyph) => { - if cursor.index == glyph.end { - return Some((run.glyphs.len(), 0.0)); - } - } - None => { - return Some((0, 0.0)); - } - } - } - None - }; - - // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some((start, end)) = self.selection_bounds() { - if line_i >= start.line && line_i <= end.line { - let mut range_opt = None; - for glyph in run.glyphs.iter() { - // Guess x offset based on characters - let cluster = &run.text[glyph.start..glyph.end]; - let total = cluster.grapheme_indices(true).count(); - let mut c_x = glyph.x; - let c_w = glyph.w / total as f32; - for (i, c) in cluster.grapheme_indices(true) { - let c_start = glyph.start + i; - let c_end = glyph.start + i + c.len(); - if (start.line != line_i || c_end > start.index) - && (end.line != line_i || c_start < end.index) - { - range_opt = match range_opt.take() { - Some((min, max)) => Some(( - cmp::min(min, c_x as i32), - cmp::max(max, (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() { - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - Color::rgba(color.r(), color.g(), color.b(), 0x33), - ); - } - c_x += c_w; - } - } - - if run.glyphs.is_empty() && end.line > line_i { - // Highlight all of internal empty lines - range_opt = Some((0, self.buffer.size().0 as i32)); - } - - if let Some((mut min, mut max)) = range_opt.take() { - if end.line > line_i { - // Draw to end of line - if run.rtl { - min = 0; - } else { - max = self.buffer.size().0 as i32; - } - } - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - Color::rgba(color.r(), color.g(), color.b(), 0x33), - ); - } - } - } - - // Draw cursor - if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) { - let x = match run.glyphs.get(cursor_glyph) { - Some(glyph) => { - // Start of detected glyph - if glyph.level.is_rtl() { - (glyph.x + glyph.w - cursor_glyph_offset) as i32 - } else { - (glyph.x + cursor_glyph_offset) as i32 - } - } - None => match run.glyphs.last() { - Some(glyph) => { - // End of last glyph - if glyph.level.is_rtl() { - glyph.x as i32 - } else { - (glyph.x + glyph.w) as i32 - } - } - None => { - // Start of empty line - 0 - } - }, - }; - - f( - x, - line_top as i32, - 1, - line_height as u32, - self.cursor.color.unwrap_or(color), - ); - } - - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((0., 0.), 1.0); - - let glyph_color = match glyph.color_opt { - Some(some) => some, - None => 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, - ); - }, - ); - } - } + self.inner.draw( + self.font_system, + cache, + text_color, + cursor_color, + selection_color, + f, + ); } } diff --git a/src/edit/mod.rs b/src/edit/mod.rs index fc938b3..aa6589e 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -3,8 +3,6 @@ use alloc::{string::String, vec::Vec}; use core::cmp; use unicode_segmentation::UnicodeSegmentation; -#[cfg(feature = "swash")] -use crate::Color; use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion}; pub use self::editor::*; @@ -265,17 +263,6 @@ pub trait Edit { /// Perform an [Action] on the editor fn action(&mut self, font_system: &mut FontSystem, action: Action); - - /// Draw the editor - #[cfg(feature = "swash")] - fn draw( - &self, - font_system: &mut FontSystem, - cache: &mut crate::SwashCache, - color: Color, - f: F, - ) where - F: FnMut(i32, i32, u32, u32, Color); } impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> { @@ -296,13 +283,4 @@ impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> { pub fn action(&mut self, action: Action) { self.inner.action(self.font_system, action); } - - /// Draw the editor - #[cfg(feature = "swash")] - pub fn draw(&mut self, cache: &mut crate::SwashCache, color: Color, f: F) - where - F: FnMut(i32, i32, u32, u32, Color), - { - self.inner.draw(self.font_system, cache, color, f); - } } diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index 750f561..ee0321a 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -159,10 +159,52 @@ impl<'a> SyntaxEditor<'a> { } } + /// Get the default cursor color + pub fn cursor_color(&self) -> Color { + if let Some(some) = self.theme.settings.caret { + Color::rgba(some.r, some.g, some.b, some.a) + } else { + self.foreground_color() + } + } + + /// Get the default selection color + pub fn selection_color(&self) -> Color { + if let Some(some) = self.theme.settings.selection { + Color::rgba(some.r, some.g, some.b, some.a) + } else { + let foreground_color = self.foreground_color(); + Color::rgba( + foreground_color.r(), + foreground_color.g(), + foreground_color.b(), + 0x33, + ) + } + } + /// Get the current syntect theme pub fn theme(&self) -> &SyntaxTheme { self.theme } + + /// Draw the editor + #[cfg(feature = "swash")] + pub fn draw(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F) + where + F: FnMut(i32, i32, u32, u32, Color), + { + let size = self.buffer().size(); + f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); + self.editor.draw( + font_system, + cache, + self.foreground_color(), + self.cursor_color(), + self.selection_color(), + f, + ); + } } impl<'a> Edit for SyntaxEditor<'a> { @@ -357,23 +399,6 @@ impl<'a> Edit for SyntaxEditor<'a> { fn action(&mut self, font_system: &mut FontSystem, action: Action) { self.editor.action(font_system, action); } - - /// Draw the editor - #[cfg(feature = "swash")] - fn draw( - &self, - font_system: &mut FontSystem, - cache: &mut crate::SwashCache, - _color: Color, - mut f: F, - ) where - F: FnMut(i32, i32, u32, u32, Color), - { - let size = self.buffer().size(); - f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); - self.editor - .draw(font_system, cache, self.foreground_color(), f); - } } impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> { @@ -386,4 +411,12 @@ impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> { pub fn load_text>(&mut self, path: P, attrs: crate::Attrs) -> io::Result<()> { self.inner.load_text(self.font_system, path, attrs) } + + #[cfg(feature = "swash")] + pub fn draw(&mut self, cache: &mut crate::SwashCache, f: F) + where + F: FnMut(i32, i32, u32, u32, Color), + { + self.inner.draw(self.font_system, cache, f); + } } diff --git a/src/edit/vi.rs b/src/edit/vi.rs index 868651b..d016698 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -201,6 +201,16 @@ impl<'a> ViEditor<'a> { self.editor.foreground_color() } + /// Get the default cursor color + pub fn cursor_color(&self) -> Color { + self.editor.cursor_color() + } + + /// Get the default selection color + pub fn selection_color(&self) -> Color { + self.editor.selection_color() + } + /// Get the current syntect theme pub fn theme(&self) -> &SyntaxTheme { self.editor.theme() @@ -248,6 +258,218 @@ impl<'a> ViEditor<'a> { self.changed = true; } } + + #[cfg(feature = "swash")] + pub fn draw(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F) + where + F: FnMut(i32, i32, u32, u32, Color), + { + let size = self.buffer().size(); + f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); + + let font_size = self.buffer().metrics().font_size; + let line_height = self.buffer().metrics().line_height; + let foreground_color = self.foreground_color(); + let cursor_color = self.cursor_color(); + let selection_color = self.selection_color(); + + for run in self.buffer().layout_runs() { + let line_i = run.line_i; + let line_y = run.line_y; + let line_top = run.line_top; + + let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> { + //TODO: better calculation of width + let default_width = font_size / 2.0; + if cursor.line == line_i { + for (glyph_i, glyph) in run.glyphs.iter().enumerate() { + if cursor.index >= glyph.start && cursor.index < glyph.end { + // Guess x offset based on characters + let mut before = 0; + let mut total = 0; + + let cluster = &run.text[glyph.start..glyph.end]; + for (i, _) in cluster.grapheme_indices(true) { + if glyph.start + i < cursor.index { + before += 1; + } + total += 1; + } + + let width = glyph.w / (total as f32); + let offset = (before as f32) * width; + return Some((glyph_i, offset, width)); + } + } + match run.glyphs.last() { + Some(glyph) => { + if cursor.index == glyph.end { + return Some((run.glyphs.len(), 0.0, default_width)); + } + } + None => { + return Some((0, 0.0, default_width)); + } + } + } + None + }; + + // Highlight selection (TODO: HIGHLIGHT COLOR!) + if let Some((start, end)) = self.selection_bounds() { + if line_i >= start.line && line_i <= end.line { + let mut range_opt = None; + for glyph in run.glyphs.iter() { + // Guess x offset based on characters + let cluster = &run.text[glyph.start..glyph.end]; + let total = cluster.grapheme_indices(true).count(); + let mut c_x = glyph.x; + let c_w = glyph.w / total as f32; + for (i, c) in cluster.grapheme_indices(true) { + let c_start = glyph.start + i; + let c_end = glyph.start + i + c.len(); + if (start.line != line_i || c_end > start.index) + && (end.line != line_i || c_start < end.index) + { + range_opt = match range_opt.take() { + Some((min, max)) => Some(( + cmp::min(min, c_x as i32), + cmp::max(max, (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() { + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + c_x += c_w; + } + } + + if run.glyphs.is_empty() && end.line > line_i { + // Highlight all of internal empty lines + range_opt = Some((0, self.buffer().size().0 as i32)); + } + + if let Some((mut min, mut max)) = range_opt.take() { + if end.line > line_i { + // Draw to end of line + if run.rtl { + min = 0; + } else { + max = self.buffer().size().0 as i32; + } + } + f( + min, + line_top as i32, + cmp::max(0, max - min) as u32, + line_height as u32, + selection_color, + ); + } + } + } + + // Draw cursor + if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = + cursor_glyph_opt(&self.cursor()) + { + let block_cursor = if self.passthrough { + false + } else { + match self.parser.mode { + ViMode::Insert | ViMode::Replace => false, + _ => true, /*TODO: determine block cursor in other modes*/ + } + }; + + let (start_x, end_x) = match run.glyphs.get(cursor_glyph) { + Some(glyph) => { + // Start of detected glyph + if glyph.level.is_rtl() { + ( + (glyph.x + glyph.w - cursor_glyph_offset) as i32, + (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) + as i32, + ) + } else { + ( + (glyph.x + cursor_glyph_offset) as i32, + (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32, + ) + } + } + None => match run.glyphs.last() { + Some(glyph) => { + // End of last glyph + if glyph.level.is_rtl() { + (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32) + } else { + ( + (glyph.x + glyph.w) as i32, + (glyph.x + glyph.w + cursor_glyph_width) as i32, + ) + } + } + None => { + // Start of empty line + (0, cursor_glyph_width as i32) + } + }, + }; + + if block_cursor { + let left_x = cmp::min(start_x, end_x); + let right_x = cmp::max(start_x, end_x); + f( + left_x, + line_top as i32, + (right_x - left_x) as u32, + line_height as u32, + selection_color, + ); + } else { + f( + start_x, + line_top as i32, + 1, + line_height as u32, + cursor_color, + ); + } + } + + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0., 0.), 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, + ); + }, + ); + } + } + } } impl<'a> Edit for ViEditor<'a> { @@ -838,214 +1060,6 @@ impl<'a> Edit for ViEditor<'a> { editor.action(font_system, action); }); } - - #[cfg(feature = "swash")] - fn draw( - &self, - font_system: &mut FontSystem, - cache: &mut crate::SwashCache, - color: Color, - mut f: F, - ) where - F: FnMut(i32, i32, u32, u32, Color), - { - let size = self.buffer().size(); - f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); - - let font_size = self.buffer().metrics().font_size; - let line_height = self.buffer().metrics().line_height; - - for run in self.buffer().layout_runs() { - let line_i = run.line_i; - let line_y = run.line_y; - let line_top = run.line_top; - - let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> { - //TODO: better calculation of width - let default_width = font_size / 2.0; - if cursor.line == line_i { - for (glyph_i, glyph) in run.glyphs.iter().enumerate() { - if cursor.index >= glyph.start && cursor.index < glyph.end { - // Guess x offset based on characters - let mut before = 0; - let mut total = 0; - - let cluster = &run.text[glyph.start..glyph.end]; - for (i, _) in cluster.grapheme_indices(true) { - if glyph.start + i < cursor.index { - before += 1; - } - total += 1; - } - - let width = glyph.w / (total as f32); - let offset = (before as f32) * width; - return Some((glyph_i, offset, width)); - } - } - match run.glyphs.last() { - Some(glyph) => { - if cursor.index == glyph.end { - return Some((run.glyphs.len(), 0.0, default_width)); - } - } - None => { - return Some((0, 0.0, default_width)); - } - } - } - None - }; - - // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some((start, end)) = self.selection_bounds() { - if line_i >= start.line && line_i <= end.line { - let mut range_opt = None; - for glyph in run.glyphs.iter() { - // Guess x offset based on characters - let cluster = &run.text[glyph.start..glyph.end]; - let total = cluster.grapheme_indices(true).count(); - let mut c_x = glyph.x; - let c_w = glyph.w / total as f32; - for (i, c) in cluster.grapheme_indices(true) { - let c_start = glyph.start + i; - let c_end = glyph.start + i + c.len(); - if (start.line != line_i || c_end > start.index) - && (end.line != line_i || c_start < end.index) - { - range_opt = match range_opt.take() { - Some((min, max)) => Some(( - cmp::min(min, c_x as i32), - cmp::max(max, (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() { - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - Color::rgba(color.r(), color.g(), color.b(), 0x33), - ); - } - c_x += c_w; - } - } - - if run.glyphs.is_empty() && end.line > line_i { - // Highlight all of internal empty lines - range_opt = Some((0, self.buffer().size().0 as i32)); - } - - if let Some((mut min, mut max)) = range_opt.take() { - if end.line > line_i { - // Draw to end of line - if run.rtl { - min = 0; - } else { - max = self.buffer().size().0 as i32; - } - } - f( - min, - line_top as i32, - cmp::max(0, max - min) as u32, - line_height as u32, - Color::rgba(color.r(), color.g(), color.b(), 0x33), - ); - } - } - } - - // Draw cursor - if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = - cursor_glyph_opt(&self.cursor()) - { - let block_cursor = if self.passthrough { - false - } else { - match self.parser.mode { - ViMode::Insert | ViMode::Replace => false, - _ => true, /*TODO: determine block cursor in other modes*/ - } - }; - - let (start_x, end_x) = match run.glyphs.get(cursor_glyph) { - Some(glyph) => { - // Start of detected glyph - if glyph.level.is_rtl() { - ( - (glyph.x + glyph.w - cursor_glyph_offset) as i32, - (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) - as i32, - ) - } else { - ( - (glyph.x + cursor_glyph_offset) as i32, - (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32, - ) - } - } - None => match run.glyphs.last() { - Some(glyph) => { - // End of last glyph - if glyph.level.is_rtl() { - (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32) - } else { - ( - (glyph.x + glyph.w) as i32, - (glyph.x + glyph.w + cursor_glyph_width) as i32, - ) - } - } - None => { - // Start of empty line - (0, cursor_glyph_width as i32) - } - }, - }; - - if block_cursor { - let left_x = cmp::min(start_x, end_x); - let right_x = cmp::max(start_x, end_x); - f( - left_x, - line_top as i32, - (right_x - left_x) as u32, - line_height as u32, - Color::rgba(color.r(), color.g(), color.b(), 0x33), - ); - } else { - f(start_x, line_top as i32, 1, line_height as u32, color); - } - } - - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((0., 0.), 1.0); - - let glyph_color = match glyph.color_opt { - Some(some) => some, - None => 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, - ); - }, - ); - } - } - } } impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> { @@ -1058,4 +1072,12 @@ impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> { ) -> std::io::Result<()> { self.inner.load_text(self.font_system, path, attrs) } + + #[cfg(feature = "swash")] + pub fn draw(&mut self, cache: &mut crate::SwashCache, f: F) + where + F: FnMut(i32, i32, u32, u32, Color), + { + self.inner.draw(self.font_system, cache, f); + } }