diff --git a/src/app.rs b/src/app.rs index 2c90728..75846b0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -571,7 +571,7 @@ fn grab_keys(ctx: &egui::Context) { } /// Corner rounding shared by a pane's border and its title bar. -const CORNER: u8 = 5; +const CORNER: u8 = theme::CORNER_RADIUS; fn show_pane( ui: &mut egui::Ui, @@ -638,7 +638,7 @@ fn show_pane( rect, CORNER, Stroke::new( - 1.0_f32, + theme::BORDER_WIDTH, if active { accent } else { diff --git a/src/pane.rs b/src/pane.rs index 11689c3..3ae13d8 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -100,6 +100,8 @@ pub fn terminal(config: &Config, theme: &Theme) -> Result { + painter.rect_filled(cursor_rect, 0, c); + // Redraw the covered glyph in the background color so an + // application's solid block cursor leaves text readable. + let ink = color(colors.background); + let cursor_painter = + painter.with_clip_rect(cursor_rect.intersect(painter.clip_rect())); + cursor_painter.extend( + self.row_cache[cursor.y as usize] + .foregrounds + .iter() + .cloned() + .map(|mut shape| { + shape.translate(rect.min.to_vec2()); + egui::epaint::shape_transform::adjust_colors( + &mut shape, + move |color| { + if *color != Color32::TRANSPARENT { + *color = ink; + } + }, + ); + shape + }), + ); + } _ => { painter.rect_stroke( cursor_rect, - 0, - Stroke::new(if active { 1.5_f32 } else { 0.75_f32 }, c), + theme::CURSOR_CORNER_RADIUS, + Stroke::new(theme::BORDER_WIDTH, c), egui::StrokeKind::Inside, ); } diff --git a/src/tests.rs b/src/tests.rs index a0d1e3b..6155263 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -28,6 +28,29 @@ fn key(key: Key, modifiers: Modifiers) -> Event { } } +#[test] +fn cursor_defaults_to_hollow_and_respects_application_styles() { + use libghostty_vt::{RenderState, render::CursorVisualStyle}; + + let mut term = pane::terminal(&Config::default(), &theme::DEFAULT).unwrap(); + let mut state = RenderState::new().unwrap(); + let snapshot = state.update(&term).unwrap(); + assert_eq!( + snapshot.cursor_visual_style().unwrap(), + CursorVisualStyle::BlockHollow + ); + for (sequence, expected) in [ + (b"\x1b[2 q", CursorVisualStyle::Block), + (b"\x1b[4 q", CursorVisualStyle::Underline), + (b"\x1b[6 q", CursorVisualStyle::Bar), + (b"\x1b[0 q", CursorVisualStyle::BlockHollow), + ] { + term.vt_write(sequence); + let snapshot = state.update(&term).unwrap(); + assert_eq!(snapshot.cursor_visual_style().unwrap(), expected); + } +} + #[test] fn terminal_preserves_unicode_colors_and_application_input_modes() { let mut term = pane::terminal(&Config::default(), &theme::DEFAULT).unwrap(); diff --git a/src/theme.rs b/src/theme.rs index 520a0dd..b4bd81c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -7,6 +7,11 @@ use libghostty_vt::style::{Palette, PaletteIndex, RgbColor}; /// integers rather than anything that needs allocating or parsing at startup. pub type Rgb = u32; +/// Shared stroke width for pane borders and the default hollow cursor. +pub const BORDER_WIDTH: f32 = 1.0; +pub const CORNER_RADIUS: u8 = 5; +pub const CURSOR_CORNER_RADIUS: u8 = 2; + /// Every color slerm draws with. A theme covers the terminal grid, the sixteen /// ANSI colors programs address by index, and the chrome around them, so that /// picking a light theme does not leave a dark tab bar behind.