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:
parent
a726a170ff
commit
b6c6cef876
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user