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 skia_safe::{Canvas, Paint, Path, Point};
use glutin::event::{Event, WindowEvent};
use skia_safe::{op, Canvas, Paint, Path, Point};
use crate::{
bridge::EditorMode,
@ -30,6 +31,13 @@ pub struct CursorSettings {
animate_in_insert_mode: bool,
animate_command_line: bool,
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_opacity: f32,
vfx_particle_lifetime: f32,
@ -48,6 +56,7 @@ impl Default for CursorSettings {
animate_in_insert_mode: true,
animate_command_line: true,
trail_size: 0.7,
unfocused_outline_width: 1.0 / 8.0,
vfx_mode: cursor_vfx::VfxMode::Disabled,
vfx_opacity: 200.0,
vfx_particle_lifetime: 1.2,
@ -174,6 +183,7 @@ pub struct CursorRenderer {
previous_editor_mode: EditorMode,
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
previous_vfx_mode: cursor_vfx::VfxMode,
window_has_focus: bool,
}
impl CursorRenderer {
@ -187,11 +197,22 @@ impl CursorRenderer {
previous_editor_mode: EditorMode::Normal,
cursor_vfx: None,
previous_vfx_mode: cursor_vfx::VfxMode::Disabled,
window_has_focus: true,
};
renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE);
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) {
self.cursor = new_cursor;
}
@ -351,17 +372,12 @@ impl CursorRenderer {
.with_a(self.cursor.alpha());
paint.set_color(background_color);
// 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);
let path = if self.window_has_focus || self.cursor.shape != CursorShape::Block {
self.draw_rectangle(canvas, &paint)
} else {
let outline_width = settings.unfocused_outline_width * grid_renderer.em_size;
self.draw_rectangular_outline(canvas, &paint, outline_width)
};
// Draw foreground
let foreground_color = self
@ -396,4 +412,54 @@ impl CursorRenderer {
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 paint: Paint,
pub default_style: Arc<Style>,
pub em_size: f32,
pub font_dimensions: Dimensions,
pub scale_factor: f64,
pub is_ready: bool,
@ -31,12 +32,14 @@ impl GridRenderer {
Some(colors::BLACK),
Some(colors::GREY),
)));
let em_size = shaper.current_size();
let font_dimensions: Dimensions = shaper.font_base_dimensions().into();
GridRenderer {
shaper,
paint,
default_style,
em_size,
font_dimensions,
scale_factor,
is_ready: false,
@ -68,6 +71,7 @@ impl GridRenderer {
}
fn update_font_dimensions(&mut self) {
self.em_size = self.shaper.current_size();
self.font_dimensions = self.shaper.font_base_dimensions().into();
self.is_ready = true;
trace!("Updated font dimensions: {:?}", self.font_dimensions,);

@ -11,6 +11,7 @@ use std::{
sync::Arc,
};
use glutin::event::Event;
use log::error;
use skia_safe::Canvas;
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> {
self.grid_renderer.font_names()
}

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

Loading…
Cancel
Save