From f811dbb00a1b47de2470951bdcf08dad2bfc72bf Mon Sep 17 00:00:00 2001 From: Cosmic Horror Date: Sun, 28 Jan 2024 12:07:50 -0700 Subject: [PATCH] feat: Support color for underlines and strikethroughs --- src/renderer.rs | 9 ++--- src/text.rs | 101 ++++++++++++++++++++++++++++++++---------------- src/utils.rs | 14 ++++++- 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/src/renderer.rs b/src/renderer.rs index 22f18992..5894a6b5 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -329,12 +329,9 @@ impl Renderer { bounds, self.zoom, ) { - let min = (line.0 .0, line.0 .1); - let max = (line.1 .0, line.1 .1 + 2. * self.hidpi_scale * self.zoom); - self.draw_rectangle( - Rect::from_min_max(min, max), - native_color(self.theme.text_color, &self.surface_format), - )?; + let min = (line.min.0, line.min.1); + let max = (line.max.0, line.max.1 + 2. * self.hidpi_scale * self.zoom); + self.draw_rectangle(Rect::from_min_max(min, max), line.color)?; } if let Some(selection) = self.selection { let (selection_rects, selection_text) = text_box.render_selection( diff --git a/src/text.rs b/src/text.rs index 1caf0e9a..b55335a0 100644 --- a/src/text.rs +++ b/src/text.rs @@ -2,6 +2,7 @@ use std::borrow::BorrowMut; use std::collections::hash_map; use std::fmt; use std::hash::{BuildHasher, Hash, Hasher}; +use std::ops::Range; use std::sync::{Arc, Mutex}; use crate::debug_impls::{self, DebugInline, DebugInlineMaybeF32Color}; @@ -9,8 +10,8 @@ use crate::utils::{Align, Line, Point, Rect, Selection, Size}; use fxhash::{FxHashMap, FxHashSet}; use glyphon::{ - Affinity, Attrs, AttrsList, BufferLine, Color, Cursor, FamilyOwned, FontSystem, Shaping, Style, - SwashCache, TextArea, TextBounds, Weight, + Affinity, Attrs, AttrsList, BufferLine, Color, Cursor, FamilyOwned, FontSystem, LayoutGlyph, + Shaping, Style, SwashCache, TextArea, TextBounds, Weight, }; use smart_debug::SmartDebug; use taffy::prelude::{AvailableSpace, Size as TaffySize}; @@ -297,13 +298,31 @@ impl TextBox { bounds: Size, zoom: f32, ) -> Vec { - let mut has_lines = false; - for text in &self.texts { - if text.is_striked || text.is_underlined { - has_lines = true; - break; - } + fn push_line_segment( + lines: &mut Vec, + current_line: Option, + glyph: &LayoutGlyph, + color: [f32; 4], + ) -> ThinLine { + let range = if let Some(current) = current_line { + if current.color == color { + let mut range = current.range; + range.end = glyph.end; + range + } else { + lines.push(current); + glyph.start..glyph.end + } + } else { + glyph.start..glyph.end + }; + ThinLine { range, color } } + + let has_lines = self + .texts + .iter() + .any(|text| text.is_striked || text.is_underlined); if !has_lines { return Vec::new(); } @@ -320,48 +339,56 @@ impl TextBox { let mut y = screen_position.1 + line_height; for line in buffer.layout_runs() { - let mut underline_ranges = Vec::new(); - let mut underline_range = None; - let mut strike_ranges = Vec::new(); - let mut strike_range = None; + let mut underlines = Vec::new(); + let mut current_underline: Option = None; + let mut strikes = Vec::new(); + let mut current_strike: Option = None; + // Goes over glyphs and finds the underlines and strikethroughs. The current + // underline/strikethrough is combined with matching consecutive lines for glyph in line.glyphs { let text = &self.texts[glyph.metadata]; + let color = text.color.unwrap_or(text.default_color); if text.is_underlined { - let mut range = underline_range.unwrap_or(glyph.start..glyph.end); - range.end = glyph.end; - underline_range = Some(range); - } else if let Some(range) = underline_range.clone() { - underline_ranges.push(range); + let underline = + push_line_segment(&mut underlines, current_underline, glyph, color); + current_underline = Some(underline); + } else if let Some(current) = current_underline.clone() { + underlines.push(current); } if text.is_striked { - let mut range = strike_range.unwrap_or(glyph.start..glyph.end); - range.end = glyph.end; - strike_range = Some(range); - } else if let Some(range) = strike_range.clone() { - strike_ranges.push(range); + let strike = push_line_segment(&mut strikes, current_strike, glyph, color); + current_strike = Some(strike); + } else if let Some(current) = current_strike.clone() { + strikes.push(current); } } - if let Some(range) = underline_range.clone() { - underline_ranges.push(range); + if let Some(current) = current_underline.take() { + underlines.push(current); } - if let Some(range) = strike_range.clone() { - strike_ranges.push(range); + if let Some(current) = current_strike.take() { + strikes.push(current); } - for underline_range in &underline_ranges { - let start_cursor = Cursor::new(line.line_i, underline_range.start); - let end_cursor = Cursor::new(line.line_i, underline_range.end); + for ThinLine { range, color } in &underlines { + let start_cursor = Cursor::new(line.line_i, range.start); + let end_cursor = Cursor::new(line.line_i, range.end); if let Some((highlight_x, highlight_w)) = line.highlight(start_cursor, end_cursor) { let x = screen_position.0 + highlight_x; - lines.push(((x.floor(), y), ((x + highlight_w).ceil(), y))); + let min = (x.floor(), y); + let max = ((x + highlight_w).ceil(), y); + let line = Line::with_color(min, max, *color); + lines.push(line); } } - for strike_range in &strike_ranges { - let start_cursor = Cursor::new(line.line_i, strike_range.start); - let end_cursor = Cursor::new(line.line_i, strike_range.end); + for ThinLine { range, color } in &strikes { + let start_cursor = Cursor::new(line.line_i, range.start); + let end_cursor = Cursor::new(line.line_i, range.end); if let Some((highlight_x, highlight_w)) = line.highlight(start_cursor, end_cursor) { let x = screen_position.0 + highlight_x; let y = y - (line_height / 2.); - lines.push(((x.floor(), y), ((x + highlight_w).ceil(), y))); + let min = (x.floor(), y); + let max = ((x + highlight_w).ceil(), y); + let line = Line::with_color(min, max, *color); + lines.push(line); } } y += line_height; @@ -455,6 +482,12 @@ impl TextBox { } } +#[derive(Clone)] +struct ThinLine { + range: Range, + color: [f32; 4], +} + #[derive(Clone)] pub struct Text { pub text: String, diff --git a/src/utils.rs b/src/utils.rs index 63ca37cd..2014d3c6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -21,12 +21,24 @@ pub fn usize_in_mib(num: usize) -> f32 { num as f32 / 1_024.0 / 1_024.0 } -pub type Line = ((f32, f32), (f32, f32)); pub type Selection = ((f32, f32), (f32, f32)); pub type Point = (f32, f32); pub type Size = (f32, f32); pub type ImageCache = Arc>>>>>; +#[derive(Debug, Clone)] +pub struct Line { + pub min: Point, + pub max: Point, + pub color: [f32; 4], +} + +impl Line { + pub fn with_color(min: Point, max: Point, color: [f32; 4]) -> Self { + Self { min, max, color } + } +} + #[derive(Debug, Clone)] pub struct Rect { pub pos: Point,