Fix cursor dimensions with font offset
Previously cursor dimensions were not calculated correctly when a font offset was specified, since the font offset was completely ignored. This has been fixed by moving all the cursor logic from the font into the Alacritty crate, applying the config's offsets before rasterizing the cursors. This has also fixed an issue with some cursors not being rendered as double-width correctly when over double-width glyphs. This fixes #2209.
This commit is contained in:
parent
a47d716daa
commit
cfc20d4f34
|
@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Resize events are not send to the shell anymore if dimensions haven't changed
|
- Resize events are not send to the shell anymore if dimensions haven't changed
|
||||||
- Minor performance issues with underline and strikeout checks
|
- Minor performance issues with underline and strikeout checks
|
||||||
- Rare bug which would extend underline and strikeout beyond the end of line
|
- Rare bug which would extend underline and strikeout beyond the end of line
|
||||||
|
- Cursors not spanning two lines when over double-width characters
|
||||||
|
- Incorrect cursor dimensions if the font offset isn't `0`
|
||||||
|
|
||||||
## Version 0.3.0
|
## Version 0.3.0
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ dependencies = [
|
||||||
name = "alacritty"
|
name = "alacritty"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraydeque 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -129,11 +128,6 @@ dependencies = [
|
||||||
"scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arraydeque"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
|
@ -2863,7 +2857,6 @@ dependencies = [
|
||||||
"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
|
"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
|
||||||
"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841"
|
"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841"
|
||||||
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
|
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
|
||||||
"checksum arraydeque 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0ffd3d69bd89910509a5d31d1f1353f38ccffdd116dd0099bbd6627f7bd8ad8"
|
|
||||||
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
|
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
|
||||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||||
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
|
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
|
||||||
|
|
|
@ -41,7 +41,6 @@ log = "0.4"
|
||||||
clap = "2"
|
clap = "2"
|
||||||
fnv = "1"
|
fnv = "1"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
arraydeque = "0.4"
|
|
||||||
glutin = { version = "0.21.0-rc2", features = ["icon_loading"] }
|
glutin = { version = "0.21.0-rc2", features = ["icon_loading"] }
|
||||||
env_logger = "0.6.0"
|
env_logger = "0.6.0"
|
||||||
base64 = "0.10.0"
|
base64 = "0.10.0"
|
||||||
|
|
|
@ -213,6 +213,7 @@ impl ::std::ops::Add for Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RasterizedGlyph {
|
pub struct RasterizedGlyph {
|
||||||
pub c: char,
|
pub c: char,
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
|
@ -314,6 +315,7 @@ impl fmt::Debug for RasterizedGlyph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct Metrics {
|
pub struct Metrics {
|
||||||
pub average_advance: f64,
|
pub average_advance: f64,
|
||||||
pub line_height: f64,
|
pub line_height: f64,
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Helpers for creating different cursor glyphs from font metrics
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
use font::{Metrics, RasterizedGlyph};
|
||||||
|
|
||||||
|
use crate::ansi::CursorStyle;
|
||||||
|
|
||||||
|
/// Width/Height of the cursor relative to the font width
|
||||||
|
pub const CURSOR_WIDTH_PERCENTAGE: i32 = 15;
|
||||||
|
|
||||||
|
pub fn get_cursor_glyph(
|
||||||
|
cursor: CursorStyle,
|
||||||
|
metrics: Metrics,
|
||||||
|
offset_x: i8,
|
||||||
|
offset_y: i8,
|
||||||
|
is_wide: bool,
|
||||||
|
) -> 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(width * CURSOR_WIDTH_PERCENTAGE / 100, 1);
|
||||||
|
|
||||||
|
// Double the cursor width if it's above a double-width glyph
|
||||||
|
if is_wide {
|
||||||
|
width *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
match cursor {
|
||||||
|
CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width),
|
||||||
|
CursorStyle::Underline => get_underline_cursor_glyph(width, line_width),
|
||||||
|
CursorStyle::Beam => get_beam_cursor_glyph(height, line_width),
|
||||||
|
CursorStyle::Block => get_block_cursor_glyph(height, width),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a custom underline cursor character
|
||||||
|
pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph {
|
||||||
|
// Create a new rectangle, the height is relative to the font width
|
||||||
|
let buf = vec![255u8; (width * line_width * 3) as usize];
|
||||||
|
|
||||||
|
// Create a custom glyph with the rectangle data attached to it
|
||||||
|
RasterizedGlyph { c: ' ', top: line_width, left: 0, height: line_width, width, buf }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a custom beam cursor character
|
||||||
|
pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph {
|
||||||
|
// Create a new rectangle that is at least one pixel wide
|
||||||
|
let buf = vec![255u8; (line_width * height * 3) as usize];
|
||||||
|
|
||||||
|
// Create a custom glyph with the rectangle data attached to it
|
||||||
|
RasterizedGlyph { c: ' ', top: height, left: 0, height, width: line_width, buf }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a custom box cursor character
|
||||||
|
pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph {
|
||||||
|
// Create a new box outline rectangle
|
||||||
|
let mut buf = Vec::with_capacity((width * height * 3) as usize);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
buf.append(&mut vec![255u8; 3]);
|
||||||
|
} else {
|
||||||
|
buf.append(&mut vec![0u8; 3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom glyph with the rectangle data attached to it
|
||||||
|
RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a custom block cursor character
|
||||||
|
pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph {
|
||||||
|
// Create a completely filled glyph
|
||||||
|
let buf = vec![255u8; (width * height * 3) as usize];
|
||||||
|
|
||||||
|
// Create a custom glyph with the rectangle data attached to it
|
||||||
|
RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf }
|
||||||
|
}
|
|
@ -435,10 +435,11 @@ impl Display {
|
||||||
let size_info = *terminal.size_info();
|
let size_info = *terminal.size_info();
|
||||||
let visual_bell_intensity = terminal.visual_bell.intensity();
|
let visual_bell_intensity = terminal.visual_bell.intensity();
|
||||||
let background_color = terminal.background_color();
|
let background_color = terminal.background_color();
|
||||||
|
let metrics = self.glyph_cache.font_metrics();
|
||||||
|
|
||||||
let window_focused = self.window.is_focused;
|
let window_focused = self.window.is_focused;
|
||||||
let grid_cells: Vec<RenderableCell> =
|
let grid_cells: Vec<RenderableCell> =
|
||||||
terminal.renderable_cells(config, window_focused).collect();
|
terminal.renderable_cells(config, window_focused, metrics).collect();
|
||||||
|
|
||||||
// Get message from terminal to ignore modifications after lock is dropped
|
// Get message from terminal to ignore modifications after lock is dropped
|
||||||
let message_buffer = terminal.message_buffer_mut().message();
|
let message_buffer = terminal.message_buffer_mut().message();
|
||||||
|
@ -479,7 +480,6 @@ impl Display {
|
||||||
|
|
||||||
{
|
{
|
||||||
let glyph_cache = &mut self.glyph_cache;
|
let glyph_cache = &mut self.glyph_cache;
|
||||||
let metrics = glyph_cache.font_metrics();
|
|
||||||
let mut rects = Rects::new(&metrics, &size_info);
|
let mut rects = Rects::new(&metrics, &size_info);
|
||||||
|
|
||||||
// Draw grid
|
// Draw grid
|
||||||
|
|
|
@ -31,6 +31,7 @@ pub mod macros;
|
||||||
pub mod ansi;
|
pub mod ansi;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
mod cursor;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod event_loop;
|
pub mod event_loop;
|
||||||
|
|
|
@ -32,7 +32,7 @@ use crate::gl::types::*;
|
||||||
use crate::index::{Column, Line};
|
use crate::index::{Column, Line};
|
||||||
use crate::renderer::rects::{Rect, Rects};
|
use crate::renderer::rects::{Rect, Rects};
|
||||||
use crate::term::color::Rgb;
|
use crate::term::color::Rgb;
|
||||||
use crate::term::{self, cell, RenderableCell};
|
use crate::term::{self, cell, RenderableCell, RenderableCellContent};
|
||||||
|
|
||||||
pub mod rects;
|
pub mod rects;
|
||||||
|
|
||||||
|
@ -950,11 +950,11 @@ impl<'a> RenderApi<'a> {
|
||||||
.map(|(i, c)| RenderableCell {
|
.map(|(i, c)| RenderableCell {
|
||||||
line,
|
line,
|
||||||
column: col + i,
|
column: col + i,
|
||||||
chars: {
|
inner: RenderableCellContent::Chars({
|
||||||
let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
|
let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
|
||||||
chars[0] = c;
|
chars[0] = c;
|
||||||
chars
|
chars
|
||||||
},
|
}),
|
||||||
bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
|
bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
|
||||||
fg: Rgb { r: 0, g: 0, b: 0 },
|
fg: Rgb { r: 0, g: 0, b: 0 },
|
||||||
flags: cell::Flags::empty(),
|
flags: cell::Flags::empty(),
|
||||||
|
@ -983,6 +983,13 @@ impl<'a> RenderApi<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
|
pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
|
||||||
|
// loader.load_glyph(&rasterized)
|
||||||
|
if let RenderableCellContent::Raw(ref raw) = cell.inner {
|
||||||
|
let glyph = self.load_glyph(raw);
|
||||||
|
self.add_render_item(&cell, &glyph);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get font key for cell
|
// Get font key for cell
|
||||||
// FIXME this is super inefficient.
|
// FIXME this is super inefficient.
|
||||||
let font_key = if cell.flags.contains(cell::Flags::BOLD) {
|
let font_key = if cell.flags.contains(cell::Flags::BOLD) {
|
||||||
|
@ -996,8 +1003,10 @@ impl<'a> RenderApi<'a> {
|
||||||
// Don't render text of HIDDEN cells
|
// Don't render text of HIDDEN cells
|
||||||
let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) {
|
let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) {
|
||||||
[' '; cell::MAX_ZEROWIDTH_CHARS + 1]
|
[' '; cell::MAX_ZEROWIDTH_CHARS + 1]
|
||||||
|
} else if let RenderableCellContent::Chars(chars) = cell.inner {
|
||||||
|
chars
|
||||||
} else {
|
} else {
|
||||||
cell.chars
|
unimplemented!();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render tabs as spaces in case the font doesn't support it
|
// Render tabs as spaces in case the font doesn't support it
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl<'a> Rects<'a> {
|
||||||
|
|
||||||
// Start a new line if the flag is present
|
// Start a new line if the flag is present
|
||||||
if cell.flags.contains(line.flag) {
|
if cell.flags.contains(line.flag) {
|
||||||
*start = *cell;
|
*start = cell.clone();
|
||||||
*end = cell.into();
|
*end = cell.into();
|
||||||
} else {
|
} else {
|
||||||
line.range = None;
|
line.range = None;
|
||||||
|
@ -105,7 +105,7 @@ impl<'a> Rects<'a> {
|
||||||
// Check for new start of line
|
// Check for new start of line
|
||||||
None => {
|
None => {
|
||||||
if cell.flags.contains(line.flag) {
|
if cell.flags.contains(line.flag) {
|
||||||
line.range = Some((*cell, cell.into()));
|
line.range = Some((cell.clone(), cell.into()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
337
src/term/mod.rs
337
src/term/mod.rs
|
@ -18,7 +18,8 @@ use std::ops::{Index, IndexMut, Range, RangeInclusive};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{io, mem, ptr};
|
use std::{io, mem, ptr};
|
||||||
|
|
||||||
use arraydeque::ArrayDeque;
|
use copypasta::{Clipboard, Load, Store};
|
||||||
|
use font::{self, RasterizedGlyph, Size};
|
||||||
use glutin::MouseCursor;
|
use glutin::MouseCursor;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ use crate::ansi::{
|
||||||
self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
|
self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
|
||||||
};
|
};
|
||||||
use crate::config::{Config, VisualBellAnimation};
|
use crate::config::{Config, VisualBellAnimation};
|
||||||
|
use crate::cursor;
|
||||||
use crate::grid::{
|
use crate::grid::{
|
||||||
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
|
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
|
||||||
ViewportPosition,
|
ViewportPosition,
|
||||||
|
@ -37,8 +39,6 @@ use crate::selection::{self, Locations, Selection};
|
||||||
use crate::term::cell::{Cell, Flags, LineLength};
|
use crate::term::cell::{Cell, Flags, LineLength};
|
||||||
use crate::term::color::Rgb;
|
use crate::term::color::Rgb;
|
||||||
use crate::url::{Url, UrlParser};
|
use crate::url::{Url, UrlParser};
|
||||||
use copypasta::{Clipboard, Load, Store};
|
|
||||||
use font::{self, Size};
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::tty;
|
use crate::tty;
|
||||||
|
@ -158,12 +158,12 @@ pub struct RenderableCellsIter<'a> {
|
||||||
grid: &'a Grid<Cell>,
|
grid: &'a Grid<Cell>,
|
||||||
cursor: &'a Point,
|
cursor: &'a Point,
|
||||||
cursor_offset: usize,
|
cursor_offset: usize,
|
||||||
mode: TermMode,
|
cursor_cell: Option<RasterizedGlyph>,
|
||||||
|
cursor_style: CursorStyle,
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
colors: &'a color::List,
|
colors: &'a color::List,
|
||||||
selection: Option<RangeInclusive<index::Linear>>,
|
selection: Option<RangeInclusive<index::Linear>>,
|
||||||
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
|
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
|
||||||
cursor_cells: ArrayDeque<[Indexed<Cell>; 3]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RenderableCellsIter<'a> {
|
impl<'a> RenderableCellsIter<'a> {
|
||||||
|
@ -176,6 +176,7 @@ impl<'a> RenderableCellsIter<'a> {
|
||||||
config: &'b Config,
|
config: &'b Config,
|
||||||
selection: Option<Locations>,
|
selection: Option<Locations>,
|
||||||
cursor_style: CursorStyle,
|
cursor_style: CursorStyle,
|
||||||
|
metrics: font::Metrics,
|
||||||
) -> RenderableCellsIter<'b> {
|
) -> RenderableCellsIter<'b> {
|
||||||
let grid = &term.grid;
|
let grid = &term.grid;
|
||||||
|
|
||||||
|
@ -224,203 +225,143 @@ impl<'a> RenderableCellsIter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load cursor glyph
|
||||||
|
let cursor = &term.cursor.point;
|
||||||
|
let cursor_cell =
|
||||||
|
if term.mode.contains(mode::TermMode::SHOW_CURSOR) && grid.contains(cursor) {
|
||||||
|
let offset_x = config.font().offset().x;
|
||||||
|
let offset_y = config.font().offset().y;
|
||||||
|
|
||||||
|
let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR)
|
||||||
|
&& (cursor.col + 1) < grid.num_cols();
|
||||||
|
Some(cursor::get_cursor_glyph(cursor_style, metrics, offset_x, offset_y, is_wide))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
RenderableCellsIter {
|
RenderableCellsIter {
|
||||||
cursor: &term.cursor.point,
|
cursor,
|
||||||
cursor_offset,
|
cursor_offset,
|
||||||
grid,
|
grid,
|
||||||
inner,
|
inner,
|
||||||
mode: term.mode,
|
|
||||||
selection: selection_range,
|
selection: selection_range,
|
||||||
url_highlight: &grid.url_highlight,
|
url_highlight: &grid.url_highlight,
|
||||||
config,
|
config,
|
||||||
colors: &term.colors,
|
colors: &term.colors,
|
||||||
cursor_cells: ArrayDeque::new(),
|
cursor_cell,
|
||||||
}
|
cursor_style,
|
||||||
.initialize(cursor_style)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_cursor_cells(&mut self, original: Cell, cursor: Cell, wide: Cell) {
|
|
||||||
// Prints the char under the cell if cursor is situated on a non-empty cell
|
|
||||||
self.cursor_cells
|
|
||||||
.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, inner: original })
|
|
||||||
.expect("won't exceed capacity");
|
|
||||||
|
|
||||||
// Prints the cursor
|
|
||||||
self.cursor_cells
|
|
||||||
.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, inner: cursor })
|
|
||||||
.expect("won't exceed capacity");
|
|
||||||
|
|
||||||
// If cursor is over a wide (2 cell size) character,
|
|
||||||
// print the second cursor cell
|
|
||||||
if self.is_wide_cursor(&cursor) {
|
|
||||||
self.cursor_cells
|
|
||||||
.push_back(Indexed {
|
|
||||||
line: self.cursor.line,
|
|
||||||
column: self.cursor.col + 1,
|
|
||||||
inner: wide,
|
|
||||||
})
|
|
||||||
.expect("won't exceed capacity");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn populate_block_cursor(&mut self) {
|
#[derive(Clone, Debug)]
|
||||||
let cell = &self.grid[self.cursor];
|
pub enum RenderableCellContent {
|
||||||
let text_color = self.config.cursor_text_color().unwrap_or(cell.bg);
|
Chars([char; cell::MAX_ZEROWIDTH_CHARS + 1]),
|
||||||
let cursor_color = self.config.cursor_cursor_color().unwrap_or(cell.fg);
|
Raw(RasterizedGlyph),
|
||||||
|
}
|
||||||
|
|
||||||
let original_cell = self.grid[self.cursor];
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RenderableCell {
|
||||||
|
/// A _Display_ line (not necessarily an _Active_ line)
|
||||||
|
pub line: Line,
|
||||||
|
pub column: Column,
|
||||||
|
pub inner: RenderableCellContent,
|
||||||
|
pub fg: Rgb,
|
||||||
|
pub bg: Rgb,
|
||||||
|
pub bg_alpha: f32,
|
||||||
|
pub flags: cell::Flags,
|
||||||
|
}
|
||||||
|
|
||||||
let mut cursor_cell = self.grid[self.cursor];
|
impl RenderableCell {
|
||||||
cursor_cell.fg = text_color;
|
fn new(config: &Config, colors: &color::List, cell: Indexed<Cell>, selected: bool) -> Self {
|
||||||
cursor_cell.bg = cursor_color;
|
// Lookup RGB values
|
||||||
|
let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags);
|
||||||
|
let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg);
|
||||||
|
|
||||||
let mut wide_cell = cursor_cell;
|
let selection_background = config.colors().selection.background;
|
||||||
wide_cell.c = ' ';
|
let bg_alpha = if let (true, Some(col)) = (selected, selection_background) {
|
||||||
|
// Override selection background with config colors
|
||||||
self.push_cursor_cells(original_cell, cursor_cell, wide_cell);
|
bg_rgb = col;
|
||||||
}
|
1.0
|
||||||
|
} else if selected ^ cell.inverse() {
|
||||||
fn populate_char_cursor(&mut self, cursor_cell_char: char, wide_cell_char: char) {
|
// Invert cell fg and bg colors
|
||||||
let original_cell = self.grid[self.cursor];
|
mem::swap(&mut fg_rgb, &mut bg_rgb);
|
||||||
|
Self::compute_bg_alpha(cell.fg)
|
||||||
let mut cursor_cell = self.grid[self.cursor];
|
|
||||||
let cursor_color = self.config.cursor_cursor_color().unwrap_or(cursor_cell.fg);
|
|
||||||
cursor_cell.c = cursor_cell_char;
|
|
||||||
cursor_cell.fg = cursor_color;
|
|
||||||
|
|
||||||
let mut wide_cell = cursor_cell;
|
|
||||||
wide_cell.c = wide_cell_char;
|
|
||||||
|
|
||||||
self.push_cursor_cells(original_cell, cursor_cell, wide_cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_underline_cursor(&mut self) {
|
|
||||||
self.populate_char_cursor(font::UNDERLINE_CURSOR_CHAR, font::UNDERLINE_CURSOR_CHAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_beam_cursor(&mut self) {
|
|
||||||
self.populate_char_cursor(font::BEAM_CURSOR_CHAR, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_box_cursor(&mut self) {
|
|
||||||
self.populate_char_cursor(font::BOX_CURSOR_CHAR, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_wide_cursor(&self, cell: &Cell) -> bool {
|
|
||||||
cell.flags.contains(cell::Flags::WIDE_CHAR) && (self.cursor.col + 1) < self.grid.num_cols()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Populates list of cursor cells with the original cell
|
|
||||||
fn populate_no_cursor(&mut self) {
|
|
||||||
self.cursor_cells
|
|
||||||
.push_back(Indexed {
|
|
||||||
line: self.cursor.line,
|
|
||||||
column: self.cursor.col,
|
|
||||||
inner: self.grid[self.cursor],
|
|
||||||
})
|
|
||||||
.expect("won't exceed capacity");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize(mut self, cursor_style: CursorStyle) -> Self {
|
|
||||||
if self.cursor_is_visible() {
|
|
||||||
match cursor_style {
|
|
||||||
CursorStyle::HollowBlock => {
|
|
||||||
self.populate_box_cursor();
|
|
||||||
},
|
|
||||||
CursorStyle::Block => {
|
|
||||||
self.populate_block_cursor();
|
|
||||||
},
|
|
||||||
CursorStyle::Beam => {
|
|
||||||
self.populate_beam_cursor();
|
|
||||||
},
|
|
||||||
CursorStyle::Underline => {
|
|
||||||
self.populate_underline_cursor();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.populate_no_cursor();
|
Self::compute_bg_alpha(cell.bg)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override selection text with config colors
|
||||||
|
if let (true, Some(col)) = (selected, config.colors().selection.text) {
|
||||||
|
fg_rgb = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderableCell {
|
||||||
|
line: cell.line,
|
||||||
|
column: cell.column,
|
||||||
|
inner: RenderableCellContent::Chars(cell.chars()),
|
||||||
|
fg: fg_rgb,
|
||||||
|
bg: bg_rgb,
|
||||||
|
bg_alpha,
|
||||||
|
flags: cell.flags,
|
||||||
}
|
}
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the cursor should be rendered.
|
fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: cell::Flags) -> Rgb {
|
||||||
#[inline]
|
|
||||||
fn cursor_is_visible(&self) -> bool {
|
|
||||||
self.mode.contains(mode::TermMode::SHOW_CURSOR) && self.grid.contains(self.cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_fg_rgb(&self, fg: Color, cell: &Cell) -> Rgb {
|
|
||||||
match fg {
|
match fg {
|
||||||
Color::Spec(rgb) => rgb,
|
Color::Spec(rgb) => rgb,
|
||||||
Color::Named(ansi) => {
|
Color::Named(ansi) => {
|
||||||
match (
|
match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) {
|
||||||
self.config.draw_bold_text_with_bright_colors(),
|
|
||||||
cell.flags & Flags::DIM_BOLD,
|
|
||||||
) {
|
|
||||||
// If no bright foreground is set, treat it like the BOLD flag doesn't exist
|
// If no bright foreground is set, treat it like the BOLD flag doesn't exist
|
||||||
(_, self::cell::Flags::DIM_BOLD)
|
(_, cell::Flags::DIM_BOLD)
|
||||||
if ansi == NamedColor::Foreground
|
if ansi == NamedColor::Foreground
|
||||||
&& self.config.colors().primary.bright_foreground.is_none() =>
|
&& config.colors().primary.bright_foreground.is_none() =>
|
||||||
{
|
{
|
||||||
self.colors[NamedColor::DimForeground]
|
colors[NamedColor::DimForeground]
|
||||||
},
|
},
|
||||||
// Draw bold text in bright colors *and* contains bold flag.
|
// Draw bold text in bright colors *and* contains bold flag.
|
||||||
(true, self::cell::Flags::BOLD) => self.colors[ansi.to_bright()],
|
(true, cell::Flags::BOLD) => colors[ansi.to_bright()],
|
||||||
// Cell is marked as dim and not bold
|
// Cell is marked as dim and not bold
|
||||||
(_, self::cell::Flags::DIM) | (false, self::cell::Flags::DIM_BOLD) => {
|
(_, cell::Flags::DIM) | (false, cell::Flags::DIM_BOLD) => colors[ansi.to_dim()],
|
||||||
self.colors[ansi.to_dim()]
|
|
||||||
},
|
|
||||||
// None of the above, keep original color.
|
// None of the above, keep original color.
|
||||||
_ => self.colors[ansi],
|
_ => colors[ansi],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Color::Indexed(idx) => {
|
Color::Indexed(idx) => {
|
||||||
let idx = match (
|
let idx = match (
|
||||||
self.config.draw_bold_text_with_bright_colors(),
|
config.draw_bold_text_with_bright_colors(),
|
||||||
cell.flags & Flags::DIM_BOLD,
|
flags & Flags::DIM_BOLD,
|
||||||
idx,
|
idx,
|
||||||
) {
|
) {
|
||||||
(true, self::cell::Flags::BOLD, 0..=7) => idx as usize + 8,
|
(true, cell::Flags::BOLD, 0..=7) => idx as usize + 8,
|
||||||
(false, self::cell::Flags::DIM, 8..=15) => idx as usize - 8,
|
(false, cell::Flags::DIM, 8..=15) => idx as usize - 8,
|
||||||
(false, self::cell::Flags::DIM, 0..=7) => idx as usize + 260,
|
(false, cell::Flags::DIM, 0..=7) => idx as usize + 260,
|
||||||
_ => idx as usize,
|
_ => idx as usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.colors[idx]
|
colors[idx]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn compute_bg_alpha(&self, bg: Color) -> f32 {
|
fn compute_bg_alpha(bg: Color) -> f32 {
|
||||||
match bg {
|
match bg {
|
||||||
Color::Named(NamedColor::Background) => 0.0,
|
Color::Named(NamedColor::Background) => 0.0,
|
||||||
_ => 1.0,
|
_ => 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_bg_rgb(&self, bg: Color) -> Rgb {
|
#[inline]
|
||||||
|
fn compute_bg_rgb(colors: &color::List, bg: Color) -> Rgb {
|
||||||
match bg {
|
match bg {
|
||||||
Color::Spec(rgb) => rgb,
|
Color::Spec(rgb) => rgb,
|
||||||
Color::Named(ansi) => self.colors[ansi],
|
Color::Named(ansi) => colors[ansi],
|
||||||
Color::Indexed(idx) => self.colors[idx],
|
Color::Indexed(idx) => colors[idx],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct RenderableCell {
|
|
||||||
/// A _Display_ line (not necessarily an _Active_ line)
|
|
||||||
pub line: Line,
|
|
||||||
pub column: Column,
|
|
||||||
pub chars: [char; cell::MAX_ZEROWIDTH_CHARS + 1],
|
|
||||||
pub fg: Rgb,
|
|
||||||
pub bg: Rgb,
|
|
||||||
pub bg_alpha: f32,
|
|
||||||
pub flags: cell::Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for RenderableCellsIter<'a> {
|
impl<'a> Iterator for RenderableCellsIter<'a> {
|
||||||
type Item = RenderableCell;
|
type Item = RenderableCell;
|
||||||
|
|
||||||
|
@ -431,23 +372,32 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
// Handle cursor
|
if self.cursor_offset == self.inner.offset() && self.inner.column() == self.cursor.col {
|
||||||
let (cell, selected, highlighted) = if self.cursor_offset == self.inner.offset()
|
// Handle cursor
|
||||||
&& self.inner.column() == self.cursor.col
|
if let Some(cursor_cell) = self.cursor_cell.take() {
|
||||||
{
|
let cell = Indexed {
|
||||||
// Cursor cell
|
inner: self.grid[self.cursor],
|
||||||
let mut cell = self.cursor_cells.pop_front().unwrap();
|
column: self.cursor.col,
|
||||||
cell.line = self.inner.line();
|
line: self.cursor.line,
|
||||||
|
};
|
||||||
|
let mut renderable_cell =
|
||||||
|
RenderableCell::new(self.config, self.colors, cell, false);
|
||||||
|
|
||||||
// Since there may be multiple cursor cells (for a wide
|
renderable_cell.inner = RenderableCellContent::Raw(cursor_cell);
|
||||||
// char), only update iteration position after all cursor
|
|
||||||
// cells have been drawn.
|
return Some(renderable_cell);
|
||||||
if self.cursor_cells.is_empty() {
|
} else {
|
||||||
self.inner.next();
|
let mut cell =
|
||||||
|
RenderableCell::new(self.config, self.colors, self.inner.next()?, false);
|
||||||
|
|
||||||
|
if self.cursor_style == CursorStyle::Block {
|
||||||
|
std::mem::swap(&mut cell.bg, &mut cell.fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(cell);
|
||||||
}
|
}
|
||||||
(cell, false, false)
|
|
||||||
} else {
|
} else {
|
||||||
let cell = self.inner.next()?;
|
let mut cell = self.inner.next()?;
|
||||||
|
|
||||||
let index = Linear::new(self.grid.num_cols(), cell.column, cell.line);
|
let index = Linear::new(self.grid.num_cols(), cell.column, cell.line);
|
||||||
|
|
||||||
|
@ -460,51 +410,13 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underline URL highlights
|
// Underline URL highlights
|
||||||
let highlighted = self
|
if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false)
|
||||||
.url_highlight
|
{
|
||||||
.as_ref()
|
cell.inner.flags.insert(Flags::UNDERLINE);
|
||||||
.map(|range| range.contains_(index))
|
}
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
(cell, selected, highlighted)
|
return Some(RenderableCell::new(self.config, self.colors, cell, selected));
|
||||||
};
|
|
||||||
|
|
||||||
// Lookup RGB values
|
|
||||||
let mut fg_rgb = self.compute_fg_rgb(cell.fg, &cell);
|
|
||||||
let mut bg_rgb = self.compute_bg_rgb(cell.bg);
|
|
||||||
|
|
||||||
let selection_background = self.config.colors().selection.background;
|
|
||||||
let bg_alpha = if let (true, Some(col)) = (selected, selection_background) {
|
|
||||||
// Override selection background with config colors
|
|
||||||
bg_rgb = col;
|
|
||||||
1.0
|
|
||||||
} else if selected ^ cell.inverse() {
|
|
||||||
// Invert cell fg and bg colors
|
|
||||||
mem::swap(&mut fg_rgb, &mut bg_rgb);
|
|
||||||
self.compute_bg_alpha(cell.fg)
|
|
||||||
} else {
|
|
||||||
self.compute_bg_alpha(cell.bg)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Override selection text with config colors
|
|
||||||
if let (true, Some(col)) = (selected, self.config.colors().selection.text) {
|
|
||||||
fg_rgb = col;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut flags = cell.flags;
|
|
||||||
if highlighted {
|
|
||||||
flags.insert(Flags::UNDERLINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(RenderableCell {
|
|
||||||
line: cell.line,
|
|
||||||
column: cell.column,
|
|
||||||
chars: cell.chars(),
|
|
||||||
fg: fg_rgb,
|
|
||||||
bg: bg_rgb,
|
|
||||||
bg_alpha,
|
|
||||||
flags,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1174,6 +1086,7 @@ impl Term {
|
||||||
&'b self,
|
&'b self,
|
||||||
config: &'b Config,
|
config: &'b Config,
|
||||||
window_focused: bool,
|
window_focused: bool,
|
||||||
|
metrics: font::Metrics,
|
||||||
) -> RenderableCellsIter<'_> {
|
) -> RenderableCellsIter<'_> {
|
||||||
let alt_screen = self.mode.contains(TermMode::ALT_SCREEN);
|
let alt_screen = self.mode.contains(TermMode::ALT_SCREEN);
|
||||||
let selection = self
|
let selection = self
|
||||||
|
@ -1189,7 +1102,7 @@ impl Term {
|
||||||
CursorStyle::HollowBlock
|
CursorStyle::HollowBlock
|
||||||
};
|
};
|
||||||
|
|
||||||
RenderableCellsIter::new(&self, config, selection, cursor)
|
RenderableCellsIter::new(&self, config, selection, cursor, metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resize terminal to new dimensions
|
/// Resize terminal to new dimensions
|
||||||
|
@ -2497,8 +2410,18 @@ mod benches {
|
||||||
let mut terminal = Term::new(&config, size, MessageBuffer::new());
|
let mut terminal = Term::new(&config, size, MessageBuffer::new());
|
||||||
mem::swap(&mut terminal.grid, &mut grid);
|
mem::swap(&mut terminal.grid, &mut grid);
|
||||||
|
|
||||||
|
let metrics = font::Metrics {
|
||||||
|
descent: 0.,
|
||||||
|
line_height: 0.,
|
||||||
|
average_advance: 0.,
|
||||||
|
underline_position: 0.,
|
||||||
|
underline_thickness: 0.,
|
||||||
|
strikeout_position: 0.,
|
||||||
|
strikeout_thickness: 0.,
|
||||||
|
};
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let iter = terminal.renderable_cells(&config, false);
|
let iter = terminal.renderable_cells(&config, false, metrics);
|
||||||
for cell in iter {
|
for cell in iter {
|
||||||
test::black_box(cell);
|
test::black_box(cell);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue