From 12fbd0051cd743bcea79f45777325f76485fd865 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 28 Dec 2020 12:45:39 +0300 Subject: Draw cursor with rect renderer This commit makes cursors being drawn via rects, thus it's always above underlines/strikeouts. Also, since the cursor isn't a glyph anymore, it can't be obscured due to atlas switching while glyphs are rendered. Fixes #4404. Fixes #3471. --- alacritty/src/cursor.rs | 168 ++++++++++++++++++---------------------- alacritty/src/display.rs | 33 +++++--- alacritty/src/input.rs | 3 +- alacritty/src/renderer/mod.rs | 58 ++++---------- alacritty/src/renderer/rects.rs | 3 +- alacritty/src/url.rs | 16 ++-- 6 files changed, 120 insertions(+), 161 deletions(-) (limited to 'alacritty') diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs index 806f6ff8..a9fba66a 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/cursor.rs @@ -1,112 +1,92 @@ -//! Helpers for creating different cursor glyphs from font metrics. - -use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph}; +//! Convert a cursor into an iterator of rects. use alacritty_terminal::ansi::CursorShape; +use alacritty_terminal::term::color::Rgb; +use alacritty_terminal::term::render::RenderableCursor; +use alacritty_terminal::term::SizeInfo; -pub fn get_cursor_glyph( - cursor: CursorShape, - metrics: Metrics, - offset_x: i8, - offset_y: i8, - is_wide: bool, - cursor_thickness: f32, -) -> RasterizedGlyph { - // Calculate the cell metrics. - // - // NOTE: With Rust 1.47+ `f64 as usize` is defined to clamp automatically: - // https://github.com/rust-lang/rust/commit/14d608f1d8a0b84da5f3bccecb3efb3d35f980dc - let height = (metrics.line_height + f64::from(offset_y)).max(1.) as usize; - let mut width = (metrics.average_advance + f64::from(offset_x)).max(1.) as usize; - let line_width = (cursor_thickness * width as f32).round().max(1.) as usize; - - // Double the cursor width if it's above a double-width glyph. - if is_wide { - width *= 2; - } +use crate::renderer::rects::RenderRect; - match cursor { - CursorShape::HollowBlock => get_box_cursor_glyph(height, width, line_width), - CursorShape::Underline => get_underline_cursor_glyph(width, line_width), - CursorShape::Beam => get_beam_cursor_glyph(height, line_width), - CursorShape::Block => get_block_cursor_glyph(height, width), - CursorShape::Hidden => RasterizedGlyph::default(), - } +/// Trait for conversion into the iterator. +pub trait IntoRects { + /// Consume the cursor for an iterator of rects. + fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects; } -/// Return a custom underline cursor character. -pub fn get_underline_cursor_glyph(width: usize, line_width: usize) -> RasterizedGlyph { - // Create a new rectangle, the height is relative to the font width. - let buffer = BitmapBuffer::RGB(vec![255u8; width * line_width * 3]); - - // Create a custom glyph with the rectangle data attached to it. - RasterizedGlyph { - character: ' ', - top: line_width as i32, - left: 0, - height: line_width as i32, - width: width as i32, - buffer, +impl IntoRects for RenderableCursor { + fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects { + let point = self.point(); + let x = point.col.0 as f32 * size_info.cell_width() + size_info.padding_x(); + let y = point.line.0 as f32 * size_info.cell_height() + size_info.padding_y(); + + let mut width = size_info.cell_width(); + let height = size_info.cell_height(); + + if self.is_wide() { + width *= 2.; + } + + let thickness = (thickness * width as f32).round().max(1.); + + match self.shape() { + CursorShape::Beam => beam(x, y, height, thickness, self.color()), + CursorShape::Underline => underline(x, y, width, height, thickness, self.color()), + CursorShape::HollowBlock => hollow(x, y, width, height, thickness, self.color()), + _ => CursorRects::default(), + } } } -/// Return a custom beam cursor character. -pub fn get_beam_cursor_glyph(height: usize, line_width: usize) -> RasterizedGlyph { - // Create a new rectangle that is at least one pixel wide - let buffer = BitmapBuffer::RGB(vec![255u8; line_width * height * 3]); - - // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { - character: ' ', - top: height as i32, - left: 0, - height: height as i32, - width: line_width as i32, - buffer, - } +/// Cursor rect iterator. +#[derive(Default)] +pub struct CursorRects { + rects: [Option; 4], + index: usize, } -/// Returns a custom box cursor character. -pub fn get_box_cursor_glyph(height: usize, width: usize, line_width: usize) -> RasterizedGlyph { - // Create a new box outline rectangle. - let mut buffer = Vec::with_capacity(width * height * 3); - for y in 0..height { - for x in 0..width { - if y < line_width - || y >= height - line_width - || x < line_width - || x >= width - line_width - { - buffer.append(&mut vec![255u8; 3]); - } else { - buffer.append(&mut vec![0u8; 3]); - } - } +impl From for CursorRects { + fn from(rect: RenderRect) -> Self { + Self { rects: [Some(rect), None, None, None], index: 0 } } +} + +impl Iterator for CursorRects { + type Item = RenderRect; - // Create a custom glyph with the rectangle data attached to it. - RasterizedGlyph { - character: ' ', - top: height as i32, - left: 0, - height: height as i32, - width: width as i32, - buffer: BitmapBuffer::RGB(buffer), + fn next(&mut self) -> Option { + let rect = self.rects.get_mut(self.index)?; + self.index += 1; + rect.take() } } -/// Return a custom block cursor character. -pub fn get_block_cursor_glyph(height: usize, width: usize) -> RasterizedGlyph { - // Create a completely filled glyph. - let buffer = BitmapBuffer::RGB(vec![255u8; width * height * 3]); - - // Create a custom glyph with the rectangle data attached to it. - RasterizedGlyph { - character: ' ', - top: height as i32, - left: 0, - height: height as i32, - width: width as i32, - buffer, +/// Create an iterator yielding a single beam rect. +fn beam(x: f32, y: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects { + RenderRect::new(x, y, thickness, height, color, 1.).into() +} + +/// Create an iterator yielding a single underline rect. +fn underline(x: f32, y: f32, width: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects { + let y = y + height - thickness; + RenderRect::new(x, y, width, thickness, color, 1.).into() +} + +/// Create an iterator yielding a rect for each side of the hollow block cursor. +fn hollow(x: f32, y: f32, width: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects { + let top_line = RenderRect::new(x, y, width, thickness, color, 1.); + + let vertical_y = y + thickness; + let vertical_height = height - 2. * thickness; + let left_line = RenderRect::new(x, vertical_y, thickness, vertical_height, color, 1.); + + let bottom_y = y + height - thickness; + let bottom_line = RenderRect::new(x, bottom_y, width, thickness, color, 1.); + + let right_x = x + width - thickness; + let right_line = RenderRect::new(right_x, vertical_y, thickness, vertical_height, color, 1.); + + CursorRects { + rects: [Some(top_line), Some(bottom_line), Some(left_line), Some(right_line)], + index: 0, } } diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 7fbf0d54..4084d639 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -33,6 +33,7 @@ use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; use crate::config::Config; +use crate::cursor::IntoRects; use crate::event::{Mouse, SearchState}; use crate::message_bar::{MessageBuffer, MessageType}; use crate::meter::Meter; @@ -246,7 +247,7 @@ impl Display { // Clear screen. let background_color = config.colors.primary.background; - renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { + renderer.with_api(&config.ui_config, &size_info, |api| { api.clear(background_color); }); @@ -268,7 +269,7 @@ impl Display { #[cfg(not(any(target_os = "macos", windows)))] if is_x11 { window.swap_buffers(); - renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { + renderer.with_api(&config.ui_config, &size_info, |api| { api.finish(); }); } @@ -450,7 +451,14 @@ impl Display { .and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match)); let cursor_hidden = self.cursor_hidden || search_state.regex().is_some(); - let grid_cells = terminal.renderable_cells(config, !cursor_hidden).collect::>(); + // Collect renderable content before the terminal is dropped. + let mut content = terminal.renderable_content(config, !cursor_hidden); + let mut grid_cells = Vec::new(); + while let Some(cell) = content.next() { + grid_cells.push(cell); + } + let cursor = content.cursor(); + let visual_bell_intensity = terminal.visual_bell.intensity(); let background_color = terminal.background_color(); let cursor_point = terminal.grid().cursor.point; @@ -471,7 +479,7 @@ impl Display { // Drop terminal as early as possible to free lock. drop(terminal); - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { + self.renderer.with_api(&config.ui_config, &size_info, |api| { api.clear(background_color); }); @@ -482,7 +490,7 @@ impl Display { { let _sampler = self.meter.sampler(); - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { // Iterate over all non-empty cells in the grid. for mut cell in grid_cells { // Invert the active match in vi-less search. @@ -538,6 +546,13 @@ impl Display { } } + // Push the cursor rects for rendering. + if let Some(cursor) = cursor { + for rect in cursor.rects(&size_info, config.cursor.thickness()) { + rects.push(rect); + } + } + // Push visual bell after url/underline/strikeout rects. if visual_bell_intensity != 0. { let visual_bell_rect = RenderRect::new( @@ -576,7 +591,7 @@ impl Display { // Relay messages to the user. let fg = config.colors.primary.background; for (i, message_text) in text.iter().enumerate() { - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, start_line + i, &message_text, fg, None); }); } @@ -621,7 +636,7 @@ impl Display { // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command // will block to synchronize (this is `glClear` in Alacritty), which causes a // permanent one frame delay. - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { + self.renderer.with_api(&config.ui_config, &size_info, |api| { api.finish(); }); } @@ -668,7 +683,7 @@ impl Display { let fg = config.colors.search_bar_foreground(); let bg = config.colors.search_bar_background(); - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, size_info.screen_lines(), &text, fg, Some(bg)); }); } @@ -684,7 +699,7 @@ impl Display { let fg = config.colors.primary.background; let bg = config.colors.normal.red; - self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, size_info.screen_lines() - 2, &timing[..], fg, Some(bg)); }); } diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 4f66721c..55799dc5 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -26,8 +26,7 @@ use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::SelectionType; -use alacritty_terminal::term::mode::TermMode; -use alacritty_terminal::term::{ClipboardType, SizeInfo, Term}; +use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index ca3553dc..70ac993b 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -15,15 +15,14 @@ use fnv::FnvHasher; use log::{debug, error, info}; use unicode_width::UnicodeWidthChar; -use alacritty_terminal::config::Cursor; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; +use alacritty_terminal::term::render::RenderableCell; +use alacritty_terminal::term::SizeInfo; use crate::config::font::{Font, FontDescription}; use crate::config::ui_config::{Delta, UIConfig}; -use crate::cursor; use crate::gl; use crate::gl::types::*; use crate::renderer::rects::{RectRenderer, RenderRect}; @@ -116,9 +115,6 @@ pub struct GlyphCache { /// Cache of buffered glyphs. cache: HashMap>, - /// Cache of buffered cursor glyphs. - cursor_cache: HashMap>, - /// Rasterizer for loading new glyphs. rasterizer: Rasterizer, @@ -164,7 +160,6 @@ impl GlyphCache { let mut cache = Self { cache: HashMap::default(), - cursor_cache: HashMap::default(), rasterizer, font_size: font.size(), font_key: regular, @@ -328,7 +323,6 @@ impl GlyphCache { pub fn clear_glyph_cache(&mut self, loader: &mut L) { loader.clear(); self.cache = HashMap::default(); - self.cursor_cache = HashMap::default(); self.load_common_glyphs(loader); } @@ -459,7 +453,6 @@ pub struct RenderApi<'a> { current_atlas: &'a mut usize, program: &'a mut TextShaderProgram, config: &'a UIConfig, - cursor_config: Cursor, } #[derive(Debug)] @@ -693,13 +686,7 @@ impl QuadRenderer { } } - pub fn with_api( - &mut self, - config: &UIConfig, - cursor_config: Cursor, - props: &SizeInfo, - func: F, - ) -> T + pub fn with_api(&mut self, config: &UIConfig, props: &SizeInfo, func: F) -> T where F: FnOnce(RenderApi<'_>) -> T, { @@ -720,7 +707,6 @@ impl QuadRenderer { current_atlas: &mut self.current_atlas, program: &mut self.program, config, - cursor_config, }); unsafe { @@ -848,10 +834,11 @@ impl<'a> RenderApi<'a> { let cells = string .chars() .enumerate() - .map(|(i, c)| RenderableCell { + .map(|(i, character)| RenderableCell { line, column: Column(i), - inner: RenderableCellContent::Chars((c, None)), + character, + zerowidth: None, flags: Flags::empty(), bg_alpha, fg, @@ -881,26 +868,6 @@ impl<'a> RenderApi<'a> { } pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) { - let (mut character, zerowidth) = match cell.inner { - RenderableCellContent::Cursor(cursor_key) => { - // Raw cell pixel buffers like cursors don't need to go through font lookup. - let metrics = glyph_cache.metrics; - let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| { - self.load_glyph(&cursor::get_cursor_glyph( - cursor_key.shape, - metrics, - self.config.font.offset.x, - self.config.font.offset.y, - cursor_key.is_wide, - self.cursor_config.thickness(), - )) - }); - self.add_render_item(&cell, glyph); - return; - }, - RenderableCellContent::Chars((c, ref mut zerowidth)) => (c, zerowidth.take()), - }; - // Get font key for cell. let font_key = match cell.flags & Flags::BOLD_ITALIC { Flags::BOLD_ITALIC => glyph_cache.bold_italic_key, @@ -911,11 +878,12 @@ impl<'a> RenderApi<'a> { // Ignore hidden cells and render tabs as spaces to prevent font issues. let hidden = cell.flags.contains(Flags::HIDDEN); - if character == '\t' || hidden { - character = ' '; + if cell.character == '\t' || hidden { + cell.character = ' '; } - let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, character }; + let mut glyph_key = + GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character }; // Add cell to batch. match glyph_cache.get(glyph_key, self) { @@ -930,9 +898,9 @@ impl<'a> RenderApi<'a> { } // Render visible zero-width characters. - if let Some(zerowidth) = zerowidth.filter(|_| !hidden) { - for character in zerowidth.iter() { - glyph_key.character = *character; + if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) { + for character in zerowidth { + glyph_key.character = character; if let Ok(glyph) = glyph_cache.get(glyph_key, self) { self.add_render_item(&cell, &glyph); } diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index 1f50da87..cfd17379 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -6,7 +6,8 @@ use crossfont::Metrics; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::{RenderableCell, SizeInfo}; +use alacritty_terminal::term::render::RenderableCell; +use alacritty_terminal::term::SizeInfo; use crate::gl; use crate::gl::types::*; diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index b33b532e..f4bf8205 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -8,7 +8,8 @@ use urlocator::{UrlLocation, UrlLocator}; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo}; +use alacritty_terminal::term::render::RenderableCell; +use alacritty_terminal::term::SizeInfo; use crate::config::Config; use crate::event::Mouse; @@ -72,12 +73,6 @@ impl Urls { // Update tracked URLs. pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) { - // Convert cell to character. - let c = match &cell.inner { - RenderableCellContent::Chars((c, _zerowidth)) => *c, - RenderableCellContent::Cursor(_) => return, - }; - let point: Point = cell.into(); let mut end = point; @@ -107,7 +102,7 @@ impl Urls { } // Advance parser. - let last_state = mem::replace(&mut self.state, self.locator.advance(c)); + let last_state = mem::replace(&mut self.state, self.locator.advance(cell.character)); match (self.state, last_state) { (UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => { // Create empty URL. @@ -204,8 +199,9 @@ mod tests { fn text_to_cells(text: &str) -> Vec { text.chars() .enumerate() - .map(|(i, c)| RenderableCell { - inner: RenderableCellContent::Chars((c, None)), + .map(|(i, character)| RenderableCell { + character, + zerowidth: None, line: Line(0), column: Column(i), fg: Default::default(), -- cgit