use std::{ops::Range, sync::Arc};
use log::trace;
use skia_safe::{
BlendMode, Canvas, Color, Color4f, HSV, Paint, PathBuilder, colors, dash_path_effect,
};
use crate::{
editor::{Colors, LineFragment, Style, UnderlineStyle},
profiling::tracy_zone,
renderer::{
CachingShaper, RendererSettings,
box_drawing::{self},
},
settings::*,
units::{
GridPos, GridScale, GridSize, PixelPos, PixelRect, PixelVec, to_skia_point, to_skia_rect,
},
window::WindowSettings,
};
use super::{box_drawing::BoxDrawingSettings, fonts::font_options::FontOptions};
pub struct GridRenderer {
pub shaper: CachingShaper,
pub default_style: Arc<Style>,
pub em_size: f32,
pub grid_scale: GridScale,
pub box_char_renderer: box_drawing::Renderer,
pub is_ready: bool,
settings: Arc<Settings>,
}
pub struct BackgroundInfo {
pub custom_color: bool,
#[allow(unused)]
pub transparent: bool,
}
impl GridRenderer {
pub fn new(scale_factor: f64, settings: Arc<Settings>) -> Self {
let mut shaper = CachingShaper::new(scale_factor as f32);
let default_style = Arc::new(Style::new(Colors::new(
Some(colors::WHITE),
Some(colors::BLACK),
Some(colors::GREY),
)));
let em_size = shaper.current_size();
let font_dimensions = shaper.font_base_dimensions();
let grid_scale = GridScale::new(font_dimensions);
let cell_size = GridSize::new(1, 1) * grid_scale;
GridRenderer {
shaper,
default_style,
em_size,
grid_scale,
box_char_renderer: box_drawing::Renderer::new(
cell_size,
em_size,
BoxDrawingSettings::default(),
),
is_ready: false,
settings,
}
}
pub fn font_names(&self) -> Vec<String> {
self.shaper.font_names()
}
pub fn handle_scale_factor_update(&mut self, scale_factor: f64) {
self.shaper.update_scale_factor(scale_factor as f32);
self.update_font_dimensions();
}
pub fn update_font(&mut self, guifont_setting: &str) {
self.shaper.update_font(guifont_setting);
self.update_font_dimensions();
}
pub fn update_font_options(&mut self, options: FontOptions) {
self.shaper.update_font_options(options);
self.update_font_dimensions();
}
pub fn update_linespace(&mut self, linespace_setting: f32) {
self.shaper.update_linespace(linespace_setting);
self.update_font_dimensions();
}
pub fn handle_box_drawing_update(&mut self, settings: BoxDrawingSettings) {
self.box_char_renderer.update_settings(settings);
}
fn update_font_dimensions(&mut self) {
self.em_size = self.shaper.current_size();
self.grid_scale = GridScale::new(self.shaper.font_base_dimensions());
let new_cell_size = GridSize::new(1, 1) * self.grid_scale;
self.box_char_renderer.update_dimensions(new_cell_size, self.em_size);
self.is_ready = true;
trace!("Updated font dimensions: {:?}", self.grid_scale);
}
fn compute_text_region(&self, cells: &Range<u32>) -> PixelRect<f32> {
let grid_position = GridPos::new(cells.start, 0);
let pos = grid_position * self.grid_scale;
let size = GridSize::new(cells.len() as i32, 1) * self.grid_scale;
PixelRect::from_origin_and_size(pos, size)
}
pub fn get_default_background_color(&self) -> Color {
self.default_style.colors.background.unwrap().to_color()
}
pub fn get_default_background(&self, opacity: f32) -> Color {
log::info!("blend {}", self.default_style.blend);
let alpha = opacity * (100 - self.default_style.blend) as f32 / 100.0;
self.get_default_background_color().with_a((alpha * 255.0) as u8)
}
pub fn background_paint_color(&self, style: &Option<Arc<Style>>, opacity: f32) -> Color4f {
let style = style.as_ref().unwrap_or(&self.default_style);
let style_background = style.background(&self.default_style.colors).to_color();
let mut paint = Paint::default();
paint.set_anti_alias(false);
paint.set_blend_mode(BlendMode::Src);
paint.set_color(style_background);
let is_default_background = style_background == self.get_default_background_color();
let normal_opacity = self.settings.get::<WindowSettings>().normal_opacity;
let alpha = if normal_opacity < 1.0 && is_default_background {
normal_opacity
} else if style.blend > 0 {
((100 - style.blend) as f32 / 100.0) * opacity
} else {
opacity
};
paint.set_alpha_f(alpha);
paint.color4f()
}
pub fn draw_background(
&mut self,
canvas: &Canvas,
cells: &Range<u32>,
style: &Option<Arc<Style>>,
opacity: f32,
) -> BackgroundInfo {
tracy_zone!("draw_background");
let debug = self.settings.get::<RendererSettings>().debug_renderer;
if style.is_none() && !debug {
return BackgroundInfo {
custom_color: false,
transparent: self.default_style.blend > 0 || opacity < 1.0,
};
}
let region = self.compute_text_region(cells);
let style = style.as_ref().unwrap_or(&self.default_style);
let style_background = style.background(&self.default_style.colors).to_color();
let mut paint = Paint::default();
paint.set_anti_alias(false);
paint.set_blend_mode(BlendMode::Src);
if debug {
let random_hsv: HSV = (rand::random::<f32>() * 360.0, 0.3, 0.3).into();
let random_color = random_hsv.to_color(255);
paint.set_color(random_color);
} else {
paint.set_color(style_background);
}
let is_default_background = style_background == self.get_default_background_color();
let normal_opacity = self.settings.get::<WindowSettings>().normal_opacity;
let alpha = if normal_opacity < 1.0 && is_default_background {
normal_opacity
} else if style.blend > 0 {
((100 - style.blend) as f32 / 100.0) * opacity
} else {
opacity
};
paint.set_alpha_f(alpha);
let custom_color = paint.color4f() != self.default_style.colors.background.unwrap();
if custom_color {
canvas.draw_rect(to_skia_rect(®ion), &paint);
}
BackgroundInfo { custom_color, transparent: alpha < 1.0 }
}
pub fn draw_foreground(
&mut self,
text_canvas: &Canvas,
boxchar_canvas: &Canvas,
fragment: &LineFragment,
window_position: PixelPos<f32>,
) -> (bool, bool) {
tracy_zone!("draw_foreground");
let LineFragment { text, cells, style, .. } = fragment;
let region = self.compute_text_region(cells);
let style = style.as_ref().unwrap_or(&self.default_style);
let mut text_drawn = false;
if let Some(underline_style) = style.underline {
let stroke_size = self.shaper.stroke_size();
let baseline_position = self.shaper.baseline_offset().round();
let underline_position =
baseline_position - self.shaper.underline_offset().min(-1.).round();
let p1 = PixelPos::new(region.min.x, underline_position);
let p2 = PixelPos::new(region.max.x, underline_position);
self.draw_underline(text_canvas, style, underline_style, stroke_size, p1, p2);
text_drawn = true;
}
if self.box_char_renderer.draw_glyph(
text,
boxchar_canvas,
region,
style.foreground(&self.default_style.colors).to_color(),
window_position,
) {
return (text_drawn, true);
} else if !text.is_empty() {
text_canvas.save();
let wider_cells = cells.start.saturating_sub(1)..cells.end + 1;
let clip_region = self.compute_text_region(&wider_cells);
text_canvas.clip_rect(to_skia_rect(&clip_region), None, Some(false));
let mut paint = Paint::default();
paint.set_anti_alias(false);
paint.set_blend_mode(BlendMode::SrcOver);
if self.settings.get::<RendererSettings>().debug_renderer {
let random_hsv: HSV = (rand::random::<f32>() * 360.0, 1.0, 1.0).into();
let random_color = random_hsv.to_color(255);
paint.set_color(random_color);
} else {
paint.set_color(style.foreground(&self.default_style.colors).to_color());
}
for word in fragment.words() {
let adjustment = PixelVec::new(
word.cell as f32 * self.grid_scale.width(),
self.shaper.baseline_offset(),
);
for blob in self.shaper.shape_cached(word, style.into()).iter() {
tracy_zone!("draw_text_blob");
text_canvas.draw_text_blob(
blob,
to_skia_point(region.min + adjustment),
&paint,
);
text_drawn = true;
}
}
if style.strikethrough {
let line_position = region.center().y;
paint.set_color(style.special(&self.default_style.colors).to_color());
text_canvas.draw_line(
(region.min.x, line_position),
(region.max.x, line_position),
&paint,
);
text_drawn = true;
}
text_canvas.restore();
}
(text_drawn, false)
}
fn draw_underline(
&self,
canvas: &Canvas,
style: &Arc<Style>,
underline_style: UnderlineStyle,
stroke_size: f32,
p1: PixelPos<f32>,
p2: PixelPos<f32>,
) {
tracy_zone!("draw_underline");
canvas.save();
let mut underline_paint = Paint::default();
underline_paint.set_anti_alias(false);
underline_paint.set_blend_mode(BlendMode::SrcOver);
let underline_stroke_scale = self.settings.get::<RendererSettings>().underline_stroke_scale;
let stroke_width = (stroke_size * underline_stroke_scale).max(1.).round();
let offset = stroke_width / 2.;
let p1 = (p1.x, p1.y + offset);
let p2 = (p2.x, p2.y + offset);
underline_paint
.set_color(style.special(&self.default_style.colors).to_color())
.set_stroke_width(stroke_width);
match underline_style {
UnderlineStyle::Underline => {
underline_paint.set_path_effect(None);
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderDouble => {
underline_paint.set_path_effect(None);
canvas.draw_line(p1, p2, &underline_paint);
let p1 = (p1.0, p1.1 + 2. * stroke_width);
let p2 = (p2.0, p2.1 + 2. * stroke_width);
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderCurl => {
let p1 = (p1.0, p1.1 + stroke_width);
let p2 = (p2.0, p2.1 + stroke_width);
underline_paint
.set_path_effect(None)
.set_anti_alias(true)
.set_style(skia_safe::paint::Style::Stroke);
let mut builder = PathBuilder::new();
builder.move_to(p1);
let mut sin = -2. * stroke_width;
let dx = self.grid_scale.width() / 2.;
let count = ((p2.0 - p1.0) / dx).round();
let dy = (p2.1 - p1.1) / count;
for _ in 0..(count as i32) {
sin *= -1.;
builder.r_quad_to((dx / 2., sin), (dx, dy));
}
let path = builder.detach();
canvas.draw_path(&path, &underline_paint);
}
UnderlineStyle::UnderDash => {
underline_paint.set_path_effect(dash_path_effect::new(
&[6.0 * stroke_width, 2.0 * stroke_width],
0.0,
));
canvas.draw_line(p1, p2, &underline_paint);
}
UnderlineStyle::UnderDot => {
underline_paint.set_path_effect(dash_path_effect::new(
&[1.0 * stroke_width, 1.0 * stroke_width],
0.0,
));
canvas.draw_line(p1, p2, &underline_paint);
}
}
canvas.restore();
}
}