1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 02:25:28 +03:00

refactor: move cursor sprite generation to be dynamic

This allocates cursor sprites as needed, and uses the Poly drawing
code from the customglyph module to do it.
This commit is contained in:
Wez Furlong 2021-08-04 10:01:21 -07:00
parent a726a170ff
commit b6c6cef876
5 changed files with 143 additions and 175 deletions

View File

@ -44,7 +44,7 @@ impl Default for CursorVisibility {
}
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CursorShape {
Default,
BlinkingBlock,

View File

@ -2,6 +2,7 @@ use crate::glyphcache::GlyphCache;
use ::window::bitmaps::atlas::Sprite;
use ::window::color::{LinearRgba, SrgbaPixel};
use std::ops::Range;
use termwiz::surface::CursorShape;
use tiny_skia::{FillRule, Paint, Path, PathBuilder, PixmapMut, Stroke, Transform};
use window::bitmaps::Texture2d;
use window::{BitmapImage, Image, Point, Rect};
@ -3626,6 +3627,104 @@ impl BlockKey {
}
impl<T: Texture2d> GlyphCache<T> {
fn draw_polys(&mut self, polys: &[Poly], buffer: &mut Image) {
let (width, height) = buffer.image_dimensions();
let mut pixmap =
PixmapMut::from_bytes(buffer.pixel_data_slice_mut(), width as u32, height as u32)
.expect("make pixmap from existing bitmap");
for Poly {
path,
intensity,
style,
} in polys
{
let intensity = (intensity.to_scale() * 255.) as u8;
let mut paint = Paint::default();
paint.set_color_rgba8(intensity, intensity, intensity, intensity);
paint.anti_alias = true;
paint.force_hq_pipeline = true;
let mut pb = PathBuilder::new();
for item in path.iter() {
item.to_skia(width, height, self.metrics.underline_height as f32, &mut pb);
}
let path = pb.finish().expect("poly path to be valid");
style.apply(
self.metrics.underline_height as f32,
&paint,
&path,
&mut pixmap,
);
}
}
pub fn cursor_sprite(&mut self, shape: Option<CursorShape>) -> anyhow::Result<Sprite<T>> {
if let Some(sprite) = self.cursor_glyphs.get(&shape) {
return Ok(sprite.clone());
}
let mut buffer = Image::new(
self.metrics.cell_size.width as usize,
self.metrics.cell_size.height as usize,
);
let black = SrgbaPixel::rgba(0, 0, 0, 0);
let cell_rect = Rect::new(Point::new(0, 0), self.metrics.cell_size);
buffer.clear_rect(cell_rect, black);
match shape {
None => {}
Some(CursorShape::Default) => {
buffer.clear_rect(cell_rect, SrgbaPixel::rgba(0xff, 0xff, 0xff, 0xff));
}
Some(CursorShape::BlinkingBlock | CursorShape::SteadyBlock) => {
self.draw_polys(
&[Poly {
path: &[
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Zero),
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Zero),
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::Zero),
],
intensity: BlockAlpha::Full,
style: PolyStyle::OutlineHeavy,
}],
&mut buffer,
);
}
Some(CursorShape::BlinkingBar | CursorShape::SteadyBar) => {
self.draw_polys(
&[Poly {
path: &[
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Zero),
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
],
intensity: BlockAlpha::Full,
style: PolyStyle::OutlineHeavy,
}],
&mut buffer,
);
}
Some(CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline) => {
self.draw_polys(
&[Poly {
path: &[
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::One),
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
],
intensity: BlockAlpha::Full,
style: PolyStyle::OutlineHeavy,
}],
&mut buffer,
);
}
}
let sprite = self.atlas.allocate(&buffer)?;
self.cursor_glyphs.insert(shape, sprite.clone());
Ok(sprite)
}
pub fn block_sprite(&mut self, block: BlockKey) -> anyhow::Result<Sprite<T>> {
let mut buffer = Image::new(
self.metrics.cell_size.width as usize,
@ -3635,40 +3734,8 @@ impl<T: Texture2d> GlyphCache<T> {
let cell_rect = Rect::new(Point::new(0, 0), self.metrics.cell_size);
fn scale(f: f32) -> usize {
f.ceil().max(1.) as usize
}
buffer.clear_rect(cell_rect, black);
// Fill a rectangular region described by the x and y ranges
let fill_rect = |buffer: &mut Image, x: Range<usize>, y: Range<usize>| {
let (width, height) = buffer.image_dimensions();
let mut pixmap =
PixmapMut::from_bytes(buffer.pixel_data_slice_mut(), width as u32, height as u32)
.expect("make pixmap from existing bitmap");
let x = x.start as f32..x.end as f32;
let y = y.start as f32..y.end as f32;
let path = PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(x.start, y.start, x.end - x.start, y.end - y.start)
.expect("valid rect"),
);
let mut paint = Paint::default();
paint.set_color(tiny_skia::Color::WHITE);
paint.force_hq_pipeline = true;
pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
};
match block {
BlockKey::Upper(num) => {
let lower = self.metrics.cell_size.height as f32 * (num as f32) / 8.;
@ -3814,37 +3881,7 @@ impl<T: Texture2d> GlyphCache<T> {
}
}
BlockKey::Poly(polys) => {
let (width, height) = buffer.image_dimensions();
let mut pixmap = PixmapMut::from_bytes(
buffer.pixel_data_slice_mut(),
width as u32,
height as u32,
)
.expect("make pixmap from existing bitmap");
for Poly {
path,
intensity,
style,
} in polys
{
let intensity = (intensity.to_scale() * 255.) as u8;
let mut paint = Paint::default();
paint.set_color_rgba8(intensity, intensity, intensity, intensity);
paint.anti_alias = true;
paint.force_hq_pipeline = true;
let mut pb = PathBuilder::new();
for item in path.iter() {
item.to_skia(width, height, self.metrics.underline_height as f32, &mut pb);
}
let path = pb.finish().expect("poly path to be valid");
style.apply(
self.metrics.underline_height as f32,
&paint,
&path,
&mut pixmap,
);
}
self.draw_polys(polys, &mut buffer);
}
}
@ -3858,3 +3895,35 @@ impl<T: Texture2d> GlyphCache<T> {
Ok(sprite)
}
}
// Fill a rectangular region described by the x and y ranges
fn fill_rect(buffer: &mut Image, x: Range<usize>, y: Range<usize>) {
let (width, height) = buffer.image_dimensions();
let mut pixmap =
PixmapMut::from_bytes(buffer.pixel_data_slice_mut(), width as u32, height as u32)
.expect("make pixmap from existing bitmap");
let x = x.start as f32..x.end as f32;
let y = y.start as f32..y.end as f32;
let path = PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(x.start, y.start, x.end - x.start, y.end - y.start)
.expect("valid rect"),
);
let mut paint = Paint::default();
paint.set_color(tiny_skia::Color::WHITE);
paint.force_hq_pipeline = true;
pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
}
fn scale(f: f32) -> usize {
f.ceil().max(1.) as usize
}

View File

@ -20,6 +20,7 @@ use std::rc::Rc;
use std::sync::{Arc, MutexGuard};
use std::time::Instant;
use termwiz::image::{ImageData, ImageDataType};
use termwiz::surface::CursorShape;
use wezterm_font::units::*;
use wezterm_font::{FontConfiguration, GlyphInfo};
use wezterm_term::Underline;
@ -201,6 +202,7 @@ pub struct GlyphCache<T: Texture2d> {
frame_cache: HashMap<[u8; 32], Sprite<T>>,
line_glyphs: HashMap<LineKey, Sprite<T>>,
pub block_glyphs: HashMap<BlockKey, Sprite<T>>,
pub cursor_glyphs: HashMap<Option<CursorShape>, Sprite<T>>,
pub metrics: RenderMetrics,
}
@ -227,6 +229,7 @@ impl GlyphCache<ImageTexture> {
metrics: metrics.clone(),
line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
})
}
}
@ -278,6 +281,7 @@ impl GlyphCache<SrgbTexture2d> {
metrics: metrics.clone(),
line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
})
}
}

View File

@ -975,8 +975,9 @@ impl super::TermWindow {
quad.set_texture(
gl_state
.util_sprites
.cursor_sprite(cursor_shape)
.glyph_cache
.borrow_mut()
.cursor_sprite(cursor_shape)?
.texture_coords(),
);
@ -1174,8 +1175,9 @@ impl super::TermWindow {
(params.filled_box, bg_color),
(
gl_state
.util_sprites
.cursor_sprite(cursor_shape)
.glyph_cache
.borrow_mut()
.cursor_sprite(cursor_shape)?
.texture_coords(),
params.cursor_border_color,
),

View File

@ -5,7 +5,6 @@ use ::window::color::SrgbaPixel;
use ::window::{Point, Rect, Size};
use anyhow::Context;
use std::rc::Rc;
use termwiz::surface::CursorShape;
use wezterm_font::units::*;
use wezterm_font::FontConfiguration;
@ -59,9 +58,6 @@ impl RenderMetrics {
pub struct UtilSprites<T: Texture2d> {
pub white_space: Sprite<T>,
pub filled_box: Sprite<T>,
pub cursor_box: Sprite<T>,
pub cursor_i_beam: Sprite<T>,
pub cursor_underline: Sprite<T>,
}
impl<T: Texture2d> UtilSprites<T> {
@ -85,112 +81,9 @@ impl<T: Texture2d> UtilSprites<T> {
buffer.clear_rect(cell_rect, black);
let white_space = glyph_cache.atlas.allocate(&buffer)?;
// Derive a width for the border box from the underline height,
// but aspect ratio adjusted for width.
let border_width = (metrics.underline_height as f64 * metrics.cell_size.width as f64
/ metrics.cell_size.height as f64)
.ceil() as usize;
buffer.clear_rect(cell_rect, black);
for i in 0..metrics.underline_height {
// Top border
buffer.draw_line(
Point::new(cell_rect.origin.x, cell_rect.origin.y + i),
Point::new(
cell_rect.origin.x + metrics.cell_size.width,
cell_rect.origin.y + i,
),
white,
);
// Bottom border
buffer.draw_line(
Point::new(
cell_rect.origin.x,
cell_rect.origin.y + metrics.cell_size.height.saturating_sub(1 + i),
),
Point::new(
cell_rect.origin.x + metrics.cell_size.width,
cell_rect.origin.y + metrics.cell_size.height.saturating_sub(1 + i),
),
white,
);
}
for i in 0..border_width {
// Left border
buffer.draw_line(
Point::new(cell_rect.origin.x + i as isize, cell_rect.origin.y),
Point::new(
cell_rect.origin.x + i as isize,
cell_rect.origin.y + metrics.cell_size.height,
),
white,
);
// Right border
buffer.draw_line(
Point::new(
cell_rect.origin.x + metrics.cell_size.width.saturating_sub(1 + i as isize),
cell_rect.origin.y,
),
Point::new(
cell_rect.origin.x + metrics.cell_size.width.saturating_sub(1 + i as isize),
cell_rect.origin.y + metrics.cell_size.height,
),
white,
);
}
let cursor_box = glyph_cache.atlas.allocate(&buffer)?;
buffer.clear_rect(cell_rect, black);
for i in 0..border_width * 2 {
// Left border
buffer.draw_line(
Point::new(cell_rect.origin.x + i as isize, cell_rect.origin.y),
Point::new(
cell_rect.origin.x + i as isize,
cell_rect.origin.y + metrics.cell_size.height,
),
white,
);
}
let cursor_i_beam = glyph_cache.atlas.allocate(&buffer)?;
buffer.clear_rect(cell_rect, black);
for i in 0..metrics.underline_height {
// Bottom border
buffer.draw_line(
Point::new(
cell_rect.origin.x,
cell_rect.origin.y + metrics.cell_size.height.saturating_sub(1 + i),
),
Point::new(
cell_rect.origin.x + metrics.cell_size.width,
cell_rect.origin.y + metrics.cell_size.height.saturating_sub(1 + i),
),
white,
);
}
let cursor_underline = glyph_cache.atlas.allocate(&buffer)?;
Ok(Self {
white_space,
filled_box,
cursor_box,
cursor_i_beam,
cursor_underline,
})
}
pub fn cursor_sprite(&self, shape: Option<CursorShape>) -> &Sprite<T> {
match shape {
None => &self.white_space,
Some(shape) => match shape {
CursorShape::Default => &self.white_space,
CursorShape::BlinkingBlock | CursorShape::SteadyBlock => &self.cursor_box,
CursorShape::BlinkingBar | CursorShape::SteadyBar => &self.cursor_i_beam,
CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline => {
&self.cursor_underline
}
},
}
}
}