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))] #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CursorShape { pub enum CursorShape {
Default, Default,
BlinkingBlock, BlinkingBlock,

View File

@ -2,6 +2,7 @@ use crate::glyphcache::GlyphCache;
use ::window::bitmaps::atlas::Sprite; use ::window::bitmaps::atlas::Sprite;
use ::window::color::{LinearRgba, SrgbaPixel}; use ::window::color::{LinearRgba, SrgbaPixel};
use std::ops::Range; use std::ops::Range;
use termwiz::surface::CursorShape;
use tiny_skia::{FillRule, Paint, Path, PathBuilder, PixmapMut, Stroke, Transform}; use tiny_skia::{FillRule, Paint, Path, PathBuilder, PixmapMut, Stroke, Transform};
use window::bitmaps::Texture2d; use window::bitmaps::Texture2d;
use window::{BitmapImage, Image, Point, Rect}; use window::{BitmapImage, Image, Point, Rect};
@ -3626,6 +3627,104 @@ impl BlockKey {
} }
impl<T: Texture2d> GlyphCache<T> { 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>> { pub fn block_sprite(&mut self, block: BlockKey) -> anyhow::Result<Sprite<T>> {
let mut buffer = Image::new( let mut buffer = Image::new(
self.metrics.cell_size.width as usize, 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); 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); 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 { match block {
BlockKey::Upper(num) => { BlockKey::Upper(num) => {
let lower = self.metrics.cell_size.height as f32 * (num as f32) / 8.; 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) => { BlockKey::Poly(polys) => {
let (width, height) = buffer.image_dimensions(); self.draw_polys(polys, &mut buffer);
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,
);
}
} }
} }
@ -3858,3 +3895,35 @@ impl<T: Texture2d> GlyphCache<T> {
Ok(sprite) 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::sync::{Arc, MutexGuard};
use std::time::Instant; use std::time::Instant;
use termwiz::image::{ImageData, ImageDataType}; use termwiz::image::{ImageData, ImageDataType};
use termwiz::surface::CursorShape;
use wezterm_font::units::*; use wezterm_font::units::*;
use wezterm_font::{FontConfiguration, GlyphInfo}; use wezterm_font::{FontConfiguration, GlyphInfo};
use wezterm_term::Underline; use wezterm_term::Underline;
@ -201,6 +202,7 @@ pub struct GlyphCache<T: Texture2d> {
frame_cache: HashMap<[u8; 32], Sprite<T>>, frame_cache: HashMap<[u8; 32], Sprite<T>>,
line_glyphs: HashMap<LineKey, Sprite<T>>, line_glyphs: HashMap<LineKey, Sprite<T>>,
pub block_glyphs: HashMap<BlockKey, Sprite<T>>, pub block_glyphs: HashMap<BlockKey, Sprite<T>>,
pub cursor_glyphs: HashMap<Option<CursorShape>, Sprite<T>>,
pub metrics: RenderMetrics, pub metrics: RenderMetrics,
} }
@ -227,6 +229,7 @@ impl GlyphCache<ImageTexture> {
metrics: metrics.clone(), metrics: metrics.clone(),
line_glyphs: HashMap::new(), line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(), block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
}) })
} }
} }
@ -278,6 +281,7 @@ impl GlyphCache<SrgbTexture2d> {
metrics: metrics.clone(), metrics: metrics.clone(),
line_glyphs: HashMap::new(), line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(), block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
}) })
} }
} }

View File

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

View File

@ -5,7 +5,6 @@ use ::window::color::SrgbaPixel;
use ::window::{Point, Rect, Size}; use ::window::{Point, Rect, Size};
use anyhow::Context; use anyhow::Context;
use std::rc::Rc; use std::rc::Rc;
use termwiz::surface::CursorShape;
use wezterm_font::units::*; use wezterm_font::units::*;
use wezterm_font::FontConfiguration; use wezterm_font::FontConfiguration;
@ -59,9 +58,6 @@ impl RenderMetrics {
pub struct UtilSprites<T: Texture2d> { pub struct UtilSprites<T: Texture2d> {
pub white_space: Sprite<T>, pub white_space: Sprite<T>,
pub filled_box: 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> { impl<T: Texture2d> UtilSprites<T> {
@ -85,112 +81,9 @@ impl<T: Texture2d> UtilSprites<T> {
buffer.clear_rect(cell_rect, black); buffer.clear_rect(cell_rect, black);
let white_space = glyph_cache.atlas.allocate(&buffer)?; 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 { Ok(Self {
white_space, white_space,
filled_box, 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
}
},
}
}
} }