Add config option to set cursor thickness

Fixes #3526.
This commit is contained in:
Kirill Chibisov 2020-04-15 06:50:34 +03:00 committed by GitHub
parent ab2db49af5
commit 33abfe34a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 36 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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<T>(
&mut self,
terminal: &mut Term<T>,
@ -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;

View File

@ -49,13 +49,14 @@ use crate::window::Window;
#[derive(Default, Clone, Debug, PartialEq)]
pub struct DisplayUpdate {
pub dimensions: Option<PhysicalSize<u32>>,
pub message_buffer: Option<()>,
pub message_buffer: bool,
pub font: Option<Font>,
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<T> 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<N: Notify + OnResize> Processor<N> {
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<N: Notify + OnResize> Processor<N> {
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<N: Notify + OnResize> Processor<N> {
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 {

View File

@ -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<L: LoadGlyph>(&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<L: LoadGlyph>(
&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<L: LoadGlyph>(&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<font::Metrics, font::Error> {
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);

View File

@ -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<HashMap<String, serde_yaml::Value>>;
@ -67,7 +68,7 @@ pub struct Config<T> {
/// 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<T> Config<T> {
#[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<CursorStyle>,
#[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<Percentage, D::Error>
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<T: Default + PartialEq + Eq> {
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
Ok(Alpha::new(f32::deserialize(deserializer)?))
Ok(Percentage::new(f32::deserialize(deserializer)?))
}
}

View File

@ -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)
}
}