Render block cursor as an outline when window is not focused (#1257)

* add method that draws a rectangular outline

* unwrap option a little more nicely

* set cursor renderer to draw outline when focus lost

* add configuraton option for cursor outline width
macos-click-through
Jesse Hallett 2 years ago committed by GitHub
parent 65c234131f
commit 86aa2751a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,7 +3,8 @@ mod cursor_vfx;
use std::collections::HashMap; use std::collections::HashMap;
use skia_safe::{Canvas, Paint, Path, Point}; use glutin::event::{Event, WindowEvent};
use skia_safe::{op, Canvas, Paint, Path, Point};
use crate::{ use crate::{
bridge::EditorMode, bridge::EditorMode,
@ -30,6 +31,13 @@ pub struct CursorSettings {
animate_in_insert_mode: bool, animate_in_insert_mode: bool,
animate_command_line: bool, animate_command_line: bool,
trail_size: f32, trail_size: f32,
/// Specify cursor outline width in ems. You probably want this to be a positive value less
/// than 0.5. If the value is <=0 then the cursor will be invisible. This setting takes effect
/// when the editor window is unfocused, at which time a block cursor will be rendered as an
/// outline instead of as a full rectangle.
unfocused_outline_width: f32,
vfx_mode: cursor_vfx::VfxMode, vfx_mode: cursor_vfx::VfxMode,
vfx_opacity: f32, vfx_opacity: f32,
vfx_particle_lifetime: f32, vfx_particle_lifetime: f32,
@ -48,6 +56,7 @@ impl Default for CursorSettings {
animate_in_insert_mode: true, animate_in_insert_mode: true,
animate_command_line: true, animate_command_line: true,
trail_size: 0.7, trail_size: 0.7,
unfocused_outline_width: 1.0 / 8.0,
vfx_mode: cursor_vfx::VfxMode::Disabled, vfx_mode: cursor_vfx::VfxMode::Disabled,
vfx_opacity: 200.0, vfx_opacity: 200.0,
vfx_particle_lifetime: 1.2, vfx_particle_lifetime: 1.2,
@ -174,6 +183,7 @@ pub struct CursorRenderer {
previous_editor_mode: EditorMode, previous_editor_mode: EditorMode,
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>, cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
previous_vfx_mode: cursor_vfx::VfxMode, previous_vfx_mode: cursor_vfx::VfxMode,
window_has_focus: bool,
} }
impl CursorRenderer { impl CursorRenderer {
@ -187,11 +197,22 @@ impl CursorRenderer {
previous_editor_mode: EditorMode::Normal, previous_editor_mode: EditorMode::Normal,
cursor_vfx: None, cursor_vfx: None,
previous_vfx_mode: cursor_vfx::VfxMode::Disabled, previous_vfx_mode: cursor_vfx::VfxMode::Disabled,
window_has_focus: true,
}; };
renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE); renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE);
renderer renderer
} }
pub fn handle_event(&mut self, event: &Event<()>) {
match event {
Event::WindowEvent {
event: WindowEvent::Focused(is_focused),
..
} => self.window_has_focus = *is_focused,
_ => {}
}
}
pub fn update_cursor(&mut self, new_cursor: Cursor) { pub fn update_cursor(&mut self, new_cursor: Cursor) {
self.cursor = new_cursor; self.cursor = new_cursor;
} }
@ -351,17 +372,12 @@ impl CursorRenderer {
.with_a(self.cursor.alpha()); .with_a(self.cursor.alpha());
paint.set_color(background_color); paint.set_color(background_color);
// The cursor is made up of four points, so I create a path with each of the four let path = if self.window_has_focus || self.cursor.shape != CursorShape::Block {
// corners. self.draw_rectangle(canvas, &paint)
let mut path = Path::new(); } else {
let outline_width = settings.unfocused_outline_width * grid_renderer.em_size;
path.move_to(self.corners[0].current_position); self.draw_rectangular_outline(canvas, &paint, outline_width)
path.line_to(self.corners[1].current_position); };
path.line_to(self.corners[2].current_position);
path.line_to(self.corners[3].current_position);
path.close();
canvas.draw_path(&path, &paint);
// Draw foreground // Draw foreground
let foreground_color = self let foreground_color = self
@ -396,4 +412,54 @@ impl CursorRenderer {
vfx.render(&settings, canvas, grid_renderer, &self.cursor); vfx.render(&settings, canvas, grid_renderer, &self.cursor);
} }
} }
fn draw_rectangle(&self, canvas: &mut Canvas, paint: &Paint) -> Path {
// The cursor is made up of four points, so I create a path with each of the four
// corners.
let mut path = Path::new();
path.move_to(self.corners[0].current_position);
path.line_to(self.corners[1].current_position);
path.line_to(self.corners[2].current_position);
path.line_to(self.corners[3].current_position);
path.close();
canvas.draw_path(&path, &paint);
path
}
fn draw_rectangular_outline(
&self,
canvas: &mut Canvas,
paint: &Paint,
outline_width: f32,
) -> Path {
let mut rectangle = Path::new();
rectangle.move_to(self.corners[0].current_position);
rectangle.line_to(self.corners[1].current_position);
rectangle.line_to(self.corners[2].current_position);
rectangle.line_to(self.corners[3].current_position);
rectangle.close();
let offsets: [Point; 4] = [
(outline_width, outline_width).into(),
(-outline_width, outline_width).into(),
(-outline_width, -outline_width).into(),
(outline_width, -outline_width).into(),
];
let mut subtract = Path::new();
subtract.move_to(self.corners[0].current_position + offsets[0]);
subtract.line_to(self.corners[1].current_position + offsets[1]);
subtract.line_to(self.corners[2].current_position + offsets[2]);
subtract.line_to(self.corners[3].current_position + offsets[3]);
subtract.close();
// We have two "rectangles"; create an outline path by subtracting the smaller rectangle
// from the larger one. This can fail in which case we return a full "rectangle".
let path = op(&rectangle, &subtract, skia_safe::PathOp::Difference).unwrap_or(rectangle);
canvas.draw_path(&path, &paint);
path
}
} }

@ -16,6 +16,7 @@ pub struct GridRenderer {
pub shaper: CachingShaper, pub shaper: CachingShaper,
pub paint: Paint, pub paint: Paint,
pub default_style: Arc<Style>, pub default_style: Arc<Style>,
pub em_size: f32,
pub font_dimensions: Dimensions, pub font_dimensions: Dimensions,
pub scale_factor: f64, pub scale_factor: f64,
pub is_ready: bool, pub is_ready: bool,
@ -31,12 +32,14 @@ impl GridRenderer {
Some(colors::BLACK), Some(colors::BLACK),
Some(colors::GREY), Some(colors::GREY),
))); )));
let em_size = shaper.current_size();
let font_dimensions: Dimensions = shaper.font_base_dimensions().into(); let font_dimensions: Dimensions = shaper.font_base_dimensions().into();
GridRenderer { GridRenderer {
shaper, shaper,
paint, paint,
default_style, default_style,
em_size,
font_dimensions, font_dimensions,
scale_factor, scale_factor,
is_ready: false, is_ready: false,
@ -68,6 +71,7 @@ impl GridRenderer {
} }
fn update_font_dimensions(&mut self) { fn update_font_dimensions(&mut self) {
self.em_size = self.shaper.current_size();
self.font_dimensions = self.shaper.font_base_dimensions().into(); self.font_dimensions = self.shaper.font_base_dimensions().into();
self.is_ready = true; self.is_ready = true;
trace!("Updated font dimensions: {:?}", self.font_dimensions,); trace!("Updated font dimensions: {:?}", self.font_dimensions,);

@ -11,6 +11,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use glutin::event::Event;
use log::error; use log::error;
use skia_safe::Canvas; use skia_safe::Canvas;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
@ -99,6 +100,10 @@ impl Renderer {
} }
} }
pub fn handle_event(&mut self, event: &Event<()>) {
self.cursor_renderer.handle_event(event);
}
pub fn font_names(&self) -> Vec<String> { pub fn font_names(&self) -> Vec<String> {
self.grid_renderer.font_names() self.grid_renderer.font_names()
} }

@ -139,6 +139,7 @@ impl GlutinWindowWrapper {
&self.renderer, &self.renderer,
&self.windowed_context, &self.windowed_context,
); );
self.renderer.handle_event(&event);
match event { match event {
Event::LoopDestroyed => { Event::LoopDestroyed => {
self.handle_quit(); self.handle_quit();

Loading…
Cancel
Save