diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e827e2..5e39a660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Default Command+N keybinding for SpawnNewInstance on macOS - Vi mode for copying text and opening links - `CopySelection` action which copies into selection buffer on Linux/BSD +- Option `cursor.thickness` to set terminal cursor thickness ### Changed diff --git a/alacritty.yml b/alacritty.yml index f0e12747..17aaa280 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -318,6 +318,10 @@ # window is not focused. #unfocused_hollow: true + # Thickness of the cursor relative to the cell width as floating point number + # from `0.0` to `1.0`. + #thickness: 0.15 + # Live config reload (changes require restart) #live_config_reload: true diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs index e18816ed..253fdaa7 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/cursor.rs @@ -20,20 +20,19 @@ use alacritty_terminal::ansi::CursorStyle; use font::{BitmapBuffer, Metrics, RasterizedGlyph}; -/// Width/Height of the cursor relative to the font width -pub const CURSOR_WIDTH_PERCENTAGE: f64 = 0.15; - pub fn get_cursor_glyph( cursor: CursorStyle, metrics: Metrics, offset_x: i8, offset_y: i8, is_wide: bool, + cursor_thickness: f64, ) -> RasterizedGlyph { // Calculate the cell metrics let height = metrics.line_height as i32 + i32::from(offset_y); let mut width = metrics.average_advance as i32 + i32::from(offset_x); - let line_width = cmp::max((f64::from(width) * CURSOR_WIDTH_PERCENTAGE).round() as i32, 1); + + let line_width = cmp::max((cursor_thickness * f64::from(width)).round() as i32, 1); // Double the cursor width if it's above a double-width glyph if is_wide { diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index c456fda8..eafab8c2 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -286,7 +286,15 @@ impl Display { size_info.cell_height = cell_height; } - /// Process update events + /// Clear glyph cache. + fn clear_glyph_cache(&mut self) { + let cache = &mut self.glyph_cache; + self.renderer.with_loader(|mut api| { + cache.clear_glyph_cache(&mut api); + }); + } + + /// Process update events. pub fn handle_update( &mut self, terminal: &mut Term, @@ -298,6 +306,8 @@ impl Display { // Update font size and cell dimensions if let Some(font) = update_pending.font { self.update_glyph_cache(config, font); + } else if update_pending.cursor { + self.clear_glyph_cache(); } let cell_width = self.size_info.cell_width; diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index e532ac12..3f2e3f77 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -49,13 +49,14 @@ use crate::window::Window; #[derive(Default, Clone, Debug, PartialEq)] pub struct DisplayUpdate { pub dimensions: Option>, - pub message_buffer: Option<()>, + pub message_buffer: bool, pub font: Option, + pub cursor: bool, } impl DisplayUpdate { fn is_empty(&self) -> bool { - self.dimensions.is_none() && self.font.is_none() && self.message_buffer.is_none() + self.dimensions.is_none() && self.font.is_none() && !self.message_buffer && !self.cursor } } @@ -255,7 +256,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } fn pop_message(&mut self) { - self.display_update_pending.message_buffer = Some(()); + self.display_update_pending.message_buffer = true; self.message_buffer.pop(); } @@ -526,7 +527,7 @@ impl Processor { Event::ConfigReload(path) => Self::reload_config(&path, processor), Event::Message(message) => { processor.ctx.message_buffer.push(message); - processor.ctx.display_update_pending.message_buffer = Some(()); + processor.ctx.display_update_pending.message_buffer = true; processor.ctx.terminal.dirty = true; }, Event::MouseCursorDirty => processor.reset_mouse_cursor(), @@ -659,7 +660,7 @@ impl Processor { T: EventListener, { processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG); - processor.ctx.display_update_pending.message_buffer = Some(()); + processor.ctx.display_update_pending.message_buffer = true; let config = match config::reload_from(&path) { Ok(config) => config, @@ -671,6 +672,13 @@ impl Processor { processor.ctx.terminal.update_config(&config); + // Reload cursor if we've changed its thickness + if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs() + > std::f64::EPSILON + { + processor.ctx.display_update_pending.cursor = true; + } + if processor.ctx.config.font != config.font { // Do not update font size if it has been changed at runtime if *processor.ctx.font_size == processor.ctx.config.font.size { diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 4526020a..caa2443f 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -213,10 +213,7 @@ impl GlyphCache { metrics, }; - cache.load_glyphs_for_font(regular, loader); - cache.load_glyphs_for_font(bold, loader); - cache.load_glyphs_for_font(italic, loader); - cache.load_glyphs_for_font(bold_italic, loader); + cache.load_common_glyphs(loader); Ok(cache) } @@ -302,17 +299,21 @@ impl GlyphCache { }) } + /// Clear currently cached data in both GL and the registry. + 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); + } + pub fn update_font_size( &mut self, font: config::Font, dpr: f64, loader: &mut L, ) -> Result<(), font::Error> { - // Clear currently cached data in both GL and the registry - loader.clear(); - self.cache = HashMap::default(); - self.cursor_cache = HashMap::default(); - // Update dpi scaling self.rasterizer.update_dpr(dpr as f32); @@ -332,10 +333,7 @@ impl GlyphCache { self.bold_italic_key = bold_italic; self.metrics = metrics; - self.load_glyphs_for_font(regular, loader); - self.load_glyphs_for_font(bold, loader); - self.load_glyphs_for_font(italic, loader); - self.load_glyphs_for_font(bold_italic, loader); + self.clear_glyph_cache(loader); Ok(()) } @@ -344,6 +342,14 @@ impl GlyphCache { self.metrics } + /// Prefetch glyphs that are almost guaranteed to be loaded anyways. + fn load_common_glyphs(&mut self, loader: &mut L) { + self.load_glyphs_for_font(self.font_key, loader); + self.load_glyphs_for_font(self.bold_italic_key, loader); + self.load_glyphs_for_font(self.italic_key, loader); + self.load_glyphs_for_font(self.bold_italic_key, loader); + } + // Calculate font metrics without access to a glyph cache pub fn static_metrics(font: Font, dpr: f64) -> Result { let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; @@ -1019,6 +1025,7 @@ impl<'a, C> RenderApi<'a, C> { self.config.font.offset.x, self.config.font.offset.y, cursor_key.is_wide, + self.config.cursor.thickness(), )) }); self.add_render_item(cell, glyph); diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index df8d37bd..53aae91a 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -40,6 +40,7 @@ use crate::term::color::Rgb; pub const LOG_TARGET_CONFIG: &str = "alacritty_config"; const MAX_SCROLLBACK_LINES: u32 = 100_000; +const DEFAULT_CURSOR_THICKNESS: f32 = 0.15; pub type MockConfig = Config>; @@ -67,7 +68,7 @@ pub struct Config { /// Background opacity from 0.0 to 1.0 #[serde(default, deserialize_with = "failure_default")] - background_opacity: Alpha, + background_opacity: Percentage, /// Window configuration #[serde(default, deserialize_with = "failure_default")] @@ -213,7 +214,7 @@ impl Config { #[inline] pub fn background_opacity(&self) -> f32 { - self.background_opacity.0 + self.background_opacity.0 as f32 } } @@ -242,20 +243,47 @@ impl Default for EscapeChars { } #[serde(default)] -#[derive(Deserialize, Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Deserialize, Copy, Clone, Debug, PartialEq)] pub struct Cursor { #[serde(deserialize_with = "failure_default")] pub style: CursorStyle, #[serde(deserialize_with = "option_explicit_none")] pub vi_mode_style: Option, + #[serde(deserialize_with = "deserialize_cursor_thickness")] + thickness: Percentage, #[serde(deserialize_with = "failure_default")] unfocused_hollow: DefaultTrueBool, } impl Cursor { + #[inline] pub fn unfocused_hollow(self) -> bool { self.unfocused_hollow.0 } + + #[inline] + pub fn thickness(self) -> f64 { + self.thickness.0 as f64 + } +} + +impl Default for Cursor { + fn default() -> Self { + Self { + style: Default::default(), + vi_mode_style: Default::default(), + thickness: Percentage::new(DEFAULT_CURSOR_THICKNESS), + unfocused_hollow: Default::default(), + } + } +} + +pub fn deserialize_cursor_thickness<'a, D>(deserializer: D) -> Result +where + D: Deserializer<'a>, +{ + Ok(Percentage::deserialize(Value::deserialize(deserializer)?) + .unwrap_or_else(|_| Percentage::new(DEFAULT_CURSOR_THICKNESS))) } #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] @@ -300,13 +328,13 @@ pub struct Delta { pub y: T, } -/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 +/// Wrapper around f32 that represents a percentage value between 0.0 and 1.0. #[derive(Clone, Copy, Debug, PartialEq)] -pub struct Alpha(f32); +pub struct Percentage(f32); -impl Alpha { +impl Percentage { pub fn new(value: f32) -> Self { - Alpha(if value < 0.0 { + Percentage(if value < 0.0 { 0.0 } else if value > 1.0 { 1.0 @@ -316,18 +344,18 @@ impl Alpha { } } -impl Default for Alpha { +impl Default for Percentage { fn default() -> Self { - Alpha(1.0) + Percentage(1.0) } } -impl<'a> Deserialize<'a> for Alpha { +impl<'a> Deserialize<'a> for Percentage { fn deserialize(deserializer: D) -> Result where D: Deserializer<'a>, { - Ok(Alpha::new(f32::deserialize(deserializer)?)) + Ok(Percentage::new(f32::deserialize(deserializer)?)) } } diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs index 358abc3b..899ca1b3 100644 --- a/alacritty_terminal/src/config/scrolling.rs +++ b/alacritty_terminal/src/config/scrolling.rs @@ -45,7 +45,7 @@ struct ScrollingMultiplier(u8); impl Default for ScrollingMultiplier { fn default() -> Self { - ScrollingMultiplier(3) + Self(3) } } @@ -54,7 +54,7 @@ struct ScrollingHistory(u32); impl Default for ScrollingHistory { fn default() -> Self { - ScrollingHistory(10_000) + Self(10_000) } }