mirror of https://github.com/sgoudham/neovide.git
commit
2815a28e68
@ -1,54 +1,72 @@
|
||||
use nvim_rs::Neovim;
|
||||
use log::trace;
|
||||
use nvim_rs::compat::tokio::Compat;
|
||||
use nvim_rs::Neovim;
|
||||
use tokio::process::ChildStdin;
|
||||
use log::trace;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UiCommand {
|
||||
Resize { width: u32, height: u32 },
|
||||
Resize {
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
Keyboard(String),
|
||||
MouseButton { action: String, position: (u32, u32) },
|
||||
Scroll { direction: String, position: (u32, u32) },
|
||||
MouseButton {
|
||||
action: String,
|
||||
position: (u32, u32),
|
||||
},
|
||||
Scroll {
|
||||
direction: String,
|
||||
position: (u32, u32),
|
||||
},
|
||||
Drag(u32, u32),
|
||||
FocusLost,
|
||||
FocusGained
|
||||
FocusGained,
|
||||
}
|
||||
|
||||
impl UiCommand {
|
||||
pub async fn execute(self, nvim: &Neovim<Compat<ChildStdin>>) {
|
||||
match self {
|
||||
UiCommand::Resize { width, height } =>
|
||||
nvim.ui_try_resize(width.max(10) as i64, height.max(3) as i64).await
|
||||
.expect("Resize failed"),
|
||||
UiCommand::Keyboard(input_command) => {
|
||||
UiCommand::Resize { width, height } => nvim
|
||||
.ui_try_resize(width.max(10) as i64, height.max(3) as i64)
|
||||
.await
|
||||
.expect("Resize failed"),
|
||||
UiCommand::Keyboard(input_command) => {
|
||||
trace!("Keyboard Input Sent: {}", input_command);
|
||||
nvim.input(&input_command).await
|
||||
.expect("Input failed");
|
||||
},
|
||||
UiCommand::MouseButton { action, position: (grid_x, grid_y) } =>
|
||||
nvim.input_mouse("left", &action, "", 0, grid_y as i64, grid_x as i64).await
|
||||
.expect("Mouse Input Failed"),
|
||||
UiCommand::Scroll { direction, position: (grid_x, grid_y) } =>
|
||||
nvim.input_mouse("wheel", &direction, "", 0, grid_y as i64, grid_x as i64).await
|
||||
.expect("Mouse Scroll Failed"),
|
||||
UiCommand::Drag(grid_x, grid_y) =>
|
||||
nvim.input_mouse("left", "drag", "", 0, grid_y as i64, grid_x as i64).await
|
||||
.expect("Mouse Drag Failed"),
|
||||
UiCommand::FocusLost => {
|
||||
nvim.command("if exists('#FocusLost') | doautocmd <nomodeline> FocusLost | endif").await
|
||||
.expect("Focus Lost Failed")
|
||||
},
|
||||
UiCommand::FocusGained => {
|
||||
nvim.command("if exists('#FocusGained') | doautocmd <nomodeline> FocusGained | endif").await
|
||||
.expect("Focus Gained Failed")
|
||||
},
|
||||
nvim.input(&input_command).await.expect("Input failed");
|
||||
}
|
||||
UiCommand::MouseButton {
|
||||
action,
|
||||
position: (grid_x, grid_y),
|
||||
} => nvim
|
||||
.input_mouse("left", &action, "", 0, grid_y as i64, grid_x as i64)
|
||||
.await
|
||||
.expect("Mouse Input Failed"),
|
||||
UiCommand::Scroll {
|
||||
direction,
|
||||
position: (grid_x, grid_y),
|
||||
} => nvim
|
||||
.input_mouse("wheel", &direction, "", 0, grid_y as i64, grid_x as i64)
|
||||
.await
|
||||
.expect("Mouse Scroll Failed"),
|
||||
UiCommand::Drag(grid_x, grid_y) => nvim
|
||||
.input_mouse("left", "drag", "", 0, grid_y as i64, grid_x as i64)
|
||||
.await
|
||||
.expect("Mouse Drag Failed"),
|
||||
UiCommand::FocusLost => nvim
|
||||
.command("if exists('#FocusLost') | doautocmd <nomodeline> FocusLost | endif")
|
||||
.await
|
||||
.expect("Focus Lost Failed"),
|
||||
UiCommand::FocusGained => nvim
|
||||
.command("if exists('#FocusGained') | doautocmd <nomodeline> FocusGained | endif")
|
||||
.await
|
||||
.expect("Focus Gained Failed"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_resize(&self) -> bool {
|
||||
match self {
|
||||
UiCommand::Resize { .. } => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,98 +1,112 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use skulpin::skia_safe::Color4f;
|
||||
|
||||
use super::style::{Style, Colors};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CursorShape {
|
||||
Block,
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
|
||||
impl CursorShape {
|
||||
pub fn from_type_name(name: &str) -> Option<CursorShape> {
|
||||
match name {
|
||||
"block" => Some(CursorShape::Block),
|
||||
"horizontal" => Some(CursorShape::Horizontal),
|
||||
"vertical" => Some(CursorShape::Vertical),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
pub struct CursorMode {
|
||||
pub shape: Option<CursorShape>,
|
||||
pub style_id: Option<u64>,
|
||||
pub cell_percentage: Option<f32>,
|
||||
pub blinkwait: Option<u64>,
|
||||
pub blinkon: Option<u64>,
|
||||
pub blinkoff: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Cursor {
|
||||
pub position: (u64, u64),
|
||||
pub shape: CursorShape,
|
||||
pub cell_percentage: Option<f32>,
|
||||
pub blinkwait: Option<u64>,
|
||||
pub blinkon: Option<u64>,
|
||||
pub blinkoff: Option<u64>,
|
||||
pub style: Option<Arc<Style>>,
|
||||
pub enabled: bool,
|
||||
pub mode_list: Vec<CursorMode>
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn new() -> Cursor {
|
||||
Cursor {
|
||||
position: (0, 0),
|
||||
shape: CursorShape::Block,
|
||||
style: None,
|
||||
cell_percentage: None,
|
||||
blinkwait: None,
|
||||
blinkon: None,
|
||||
blinkoff: None,
|
||||
enabled: true,
|
||||
mode_list: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn foreground(&self, default_colors: &Colors) -> Color4f {
|
||||
if let Some(style) = &self.style {
|
||||
style.colors.foreground.clone().unwrap_or_else(||default_colors.background.clone().unwrap())
|
||||
} else {
|
||||
default_colors.background.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background(&self, default_colors: &Colors) -> Color4f {
|
||||
if let Some(style) = &self.style {
|
||||
style.colors.background.clone().unwrap_or_else(||default_colors.foreground.clone().unwrap())
|
||||
} else {
|
||||
default_colors.foreground.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_mode(&mut self, mode_index: u64, styles: &HashMap<u64, Arc<Style>>) {
|
||||
if let Some(CursorMode { shape, style_id, cell_percentage, blinkwait, blinkon, blinkoff }) = self.mode_list.get(mode_index as usize) {
|
||||
if let Some(shape) = shape {
|
||||
self.shape = shape.clone();
|
||||
}
|
||||
|
||||
if let Some(style_id) = style_id {
|
||||
self.style = styles
|
||||
.get(style_id)
|
||||
.cloned();
|
||||
}
|
||||
|
||||
self.cell_percentage = *cell_percentage;
|
||||
self.blinkwait = *blinkwait;
|
||||
self.blinkon = *blinkon;
|
||||
self.blinkoff = *blinkoff;
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use skulpin::skia_safe::Color4f;
|
||||
|
||||
use super::style::{Colors, Style};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CursorShape {
|
||||
Block,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl CursorShape {
|
||||
pub fn from_type_name(name: &str) -> Option<CursorShape> {
|
||||
match name {
|
||||
"block" => Some(CursorShape::Block),
|
||||
"horizontal" => Some(CursorShape::Horizontal),
|
||||
"vertical" => Some(CursorShape::Vertical),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
pub struct CursorMode {
|
||||
pub shape: Option<CursorShape>,
|
||||
pub style_id: Option<u64>,
|
||||
pub cell_percentage: Option<f32>,
|
||||
pub blinkwait: Option<u64>,
|
||||
pub blinkon: Option<u64>,
|
||||
pub blinkoff: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Cursor {
|
||||
pub position: (u64, u64),
|
||||
pub shape: CursorShape,
|
||||
pub cell_percentage: Option<f32>,
|
||||
pub blinkwait: Option<u64>,
|
||||
pub blinkon: Option<u64>,
|
||||
pub blinkoff: Option<u64>,
|
||||
pub style: Option<Arc<Style>>,
|
||||
pub enabled: bool,
|
||||
pub mode_list: Vec<CursorMode>,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn new() -> Cursor {
|
||||
Cursor {
|
||||
position: (0, 0),
|
||||
shape: CursorShape::Block,
|
||||
style: None,
|
||||
cell_percentage: None,
|
||||
blinkwait: None,
|
||||
blinkon: None,
|
||||
blinkoff: None,
|
||||
enabled: true,
|
||||
mode_list: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn foreground(&self, default_colors: &Colors) -> Color4f {
|
||||
if let Some(style) = &self.style {
|
||||
style
|
||||
.colors
|
||||
.foreground
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.background.clone().unwrap())
|
||||
} else {
|
||||
default_colors.background.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background(&self, default_colors: &Colors) -> Color4f {
|
||||
if let Some(style) = &self.style {
|
||||
style
|
||||
.colors
|
||||
.background
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.foreground.clone().unwrap())
|
||||
} else {
|
||||
default_colors.foreground.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_mode(&mut self, mode_index: u64, styles: &HashMap<u64, Arc<Style>>) {
|
||||
if let Some(CursorMode {
|
||||
shape,
|
||||
style_id,
|
||||
cell_percentage,
|
||||
blinkwait,
|
||||
blinkon,
|
||||
blinkoff,
|
||||
}) = self.mode_list.get(mode_index as usize)
|
||||
{
|
||||
if let Some(shape) = shape {
|
||||
self.shape = shape.clone();
|
||||
}
|
||||
|
||||
if let Some(style_id) = style_id {
|
||||
self.style = styles.get(style_id).cloned();
|
||||
}
|
||||
|
||||
self.cell_percentage = *cell_percentage;
|
||||
self.blinkwait = *blinkwait;
|
||||
self.blinkon = *blinkon;
|
||||
self.blinkoff = *blinkoff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,64 @@
|
||||
use skulpin::skia_safe::Color4f;
|
||||
|
||||
#[derive(new, PartialEq, Debug, Clone)]
|
||||
pub struct Colors {
|
||||
pub foreground: Option<Color4f>,
|
||||
pub background: Option<Color4f>,
|
||||
pub special: Option<Color4f>
|
||||
}
|
||||
|
||||
#[derive(new, Debug, Clone, PartialEq)]
|
||||
pub struct Style {
|
||||
pub colors: Colors,
|
||||
#[new(default)]
|
||||
pub reverse: bool,
|
||||
#[new(default)]
|
||||
pub italic: bool,
|
||||
#[new(default)]
|
||||
pub bold: bool,
|
||||
#[new(default)]
|
||||
pub strikethrough: bool,
|
||||
#[new(default)]
|
||||
pub underline: bool,
|
||||
#[new(default)]
|
||||
pub undercurl: bool,
|
||||
#[new(default)]
|
||||
pub blend: u8
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn foreground(&self, default_colors: &Colors) -> Color4f {
|
||||
if self.reverse {
|
||||
self.colors.background.clone().unwrap_or_else(||default_colors.background.clone().unwrap())
|
||||
} else {
|
||||
self.colors.foreground.clone().unwrap_or_else(||default_colors.foreground.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background(&self, default_colors: &Colors) -> Color4f {
|
||||
if self.reverse {
|
||||
self.colors.foreground.clone().unwrap_or_else(||default_colors.foreground.clone().unwrap())
|
||||
} else {
|
||||
self.colors.background.clone().unwrap_or_else(||default_colors.background.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn special(&self, default_colors: &Colors) -> Color4f {
|
||||
self.colors.special.clone().unwrap_or_else(||default_colors.special.clone().unwrap())
|
||||
}
|
||||
}
|
||||
use skulpin::skia_safe::Color4f;
|
||||
|
||||
#[derive(new, PartialEq, Debug, Clone)]
|
||||
pub struct Colors {
|
||||
pub foreground: Option<Color4f>,
|
||||
pub background: Option<Color4f>,
|
||||
pub special: Option<Color4f>,
|
||||
}
|
||||
|
||||
#[derive(new, Debug, Clone, PartialEq)]
|
||||
pub struct Style {
|
||||
pub colors: Colors,
|
||||
#[new(default)]
|
||||
pub reverse: bool,
|
||||
#[new(default)]
|
||||
pub italic: bool,
|
||||
#[new(default)]
|
||||
pub bold: bool,
|
||||
#[new(default)]
|
||||
pub strikethrough: bool,
|
||||
#[new(default)]
|
||||
pub underline: bool,
|
||||
#[new(default)]
|
||||
pub undercurl: bool,
|
||||
#[new(default)]
|
||||
pub blend: u8,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn foreground(&self, default_colors: &Colors) -> Color4f {
|
||||
if self.reverse {
|
||||
self.colors
|
||||
.background
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.background.clone().unwrap())
|
||||
} else {
|
||||
self.colors
|
||||
.foreground
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.foreground.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background(&self, default_colors: &Colors) -> Color4f {
|
||||
if self.reverse {
|
||||
self.colors
|
||||
.foreground
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.foreground.clone().unwrap())
|
||||
} else {
|
||||
self.colors
|
||||
.background
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.background.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn special(&self, default_colors: &Colors) -> Color4f {
|
||||
self.colors
|
||||
.special
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_colors.special.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
use log::error;
|
||||
|
||||
fn show_error(explanation: &str) -> ! {
|
||||
error!("{}", explanation);
|
||||
panic!(explanation.to_string());
|
||||
}
|
||||
|
||||
pub trait ResultPanicExplanation<T, E: ToString> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T;
|
||||
}
|
||||
|
||||
impl<T, E: ToString> ResultPanicExplanation<T, E> for Result<T, E> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T {
|
||||
match self {
|
||||
Err(error) => {
|
||||
let explanation = format!("{}: {}", explanation, error.to_string());
|
||||
show_error(&explanation);
|
||||
},
|
||||
Ok(content) => content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionPanicExplanation<T> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T;
|
||||
}
|
||||
|
||||
impl<T> OptionPanicExplanation<T> for Option<T> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T {
|
||||
match self {
|
||||
None => {
|
||||
show_error(explanation);
|
||||
},
|
||||
Some(content) => content
|
||||
}
|
||||
}
|
||||
}
|
||||
use log::error;
|
||||
|
||||
fn show_error(explanation: &str) -> ! {
|
||||
error!("{}", explanation);
|
||||
panic!(explanation.to_string());
|
||||
}
|
||||
|
||||
pub trait ResultPanicExplanation<T, E: ToString> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T;
|
||||
}
|
||||
|
||||
impl<T, E: ToString> ResultPanicExplanation<T, E> for Result<T, E> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T {
|
||||
match self {
|
||||
Err(error) => {
|
||||
let explanation = format!("{}: {}", explanation, error.to_string());
|
||||
show_error(&explanation);
|
||||
}
|
||||
Ok(content) => content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionPanicExplanation<T> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T;
|
||||
}
|
||||
|
||||
impl<T> OptionPanicExplanation<T> for Option<T> {
|
||||
fn unwrap_or_explained_panic(self, explanation: &str) -> T {
|
||||
match self {
|
||||
None => {
|
||||
show_error(explanation);
|
||||
}
|
||||
Some(content) => content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,87 @@
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::time::Instant;
|
||||
|
||||
use log::trace;
|
||||
|
||||
use crate::settings::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref REDRAW_SCHEDULER: RedrawScheduler = RedrawScheduler::new();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RedrawSettings {
|
||||
extra_buffer_frames: u64,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
|
||||
let buffer_frames = if SETTINGS.neovim_arguments.contains(&String::from("--extraBufferFrames")) {
|
||||
60
|
||||
}else{
|
||||
1
|
||||
};
|
||||
|
||||
SETTINGS.set(&RedrawSettings {
|
||||
extra_buffer_frames: buffer_frames,
|
||||
});
|
||||
|
||||
register_nvim_setting!("extra_buffer_frames", RedrawSettings::extra_buffer_frames);
|
||||
}
|
||||
|
||||
pub struct RedrawScheduler {
|
||||
frames_queued: AtomicU16,
|
||||
scheduled_frame: Mutex<Option<Instant>>
|
||||
}
|
||||
|
||||
impl RedrawScheduler {
|
||||
pub fn new() -> RedrawScheduler {
|
||||
RedrawScheduler {
|
||||
frames_queued: AtomicU16::new(1),
|
||||
scheduled_frame: Mutex::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule(&self, new_scheduled: Instant) {
|
||||
trace!("Redraw scheduled for {:?}", new_scheduled);
|
||||
let mut scheduled_frame = self.scheduled_frame.lock().unwrap();
|
||||
if let Some(previous_scheduled) = *scheduled_frame {
|
||||
if new_scheduled < previous_scheduled {
|
||||
*scheduled_frame = Some(new_scheduled);
|
||||
}
|
||||
} else {
|
||||
*scheduled_frame = Some(new_scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_next_frame(&self) {
|
||||
trace!("Next frame queued");
|
||||
let buffer_frames = SETTINGS.get::<RedrawSettings>().extra_buffer_frames;
|
||||
self.frames_queued.store(buffer_frames as u16, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn should_draw(&self) -> bool {
|
||||
let frames_queued = self.frames_queued.load(Ordering::Relaxed);
|
||||
if frames_queued > 0 {
|
||||
self.frames_queued.store(frames_queued - 1, Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
let mut next_scheduled_frame = self.scheduled_frame.lock().unwrap();
|
||||
if let Some(scheduled_frame) = *next_scheduled_frame {
|
||||
if scheduled_frame < Instant::now() {
|
||||
*next_scheduled_frame = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::time::Instant;
|
||||
|
||||
use log::trace;
|
||||
|
||||
use crate::settings::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref REDRAW_SCHEDULER: RedrawScheduler = RedrawScheduler::new();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RedrawSettings {
|
||||
extra_buffer_frames: u64,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
let buffer_frames = if SETTINGS
|
||||
.neovim_arguments
|
||||
.contains(&String::from("--extraBufferFrames"))
|
||||
{
|
||||
60
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
SETTINGS.set(&RedrawSettings {
|
||||
extra_buffer_frames: buffer_frames,
|
||||
});
|
||||
|
||||
register_nvim_setting!("extra_buffer_frames", RedrawSettings::extra_buffer_frames);
|
||||
}
|
||||
|
||||
pub struct RedrawScheduler {
|
||||
frames_queued: AtomicU16,
|
||||
scheduled_frame: Mutex<Option<Instant>>,
|
||||
}
|
||||
|
||||
impl RedrawScheduler {
|
||||
pub fn new() -> RedrawScheduler {
|
||||
RedrawScheduler {
|
||||
frames_queued: AtomicU16::new(1),
|
||||
scheduled_frame: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule(&self, new_scheduled: Instant) {
|
||||
trace!("Redraw scheduled for {:?}", new_scheduled);
|
||||
let mut scheduled_frame = self.scheduled_frame.lock().unwrap();
|
||||
if let Some(previous_scheduled) = *scheduled_frame {
|
||||
if new_scheduled < previous_scheduled {
|
||||
*scheduled_frame = Some(new_scheduled);
|
||||
}
|
||||
} else {
|
||||
*scheduled_frame = Some(new_scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_next_frame(&self) {
|
||||
trace!("Next frame queued");
|
||||
let buffer_frames = SETTINGS.get::<RedrawSettings>().extra_buffer_frames;
|
||||
self.frames_queued
|
||||
.store(buffer_frames as u16, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn should_draw(&self) -> bool {
|
||||
let frames_queued = self.frames_queued.load(Ordering::Relaxed);
|
||||
if frames_queued > 0 {
|
||||
self.frames_queued
|
||||
.store(frames_queued - 1, Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
let mut next_scheduled_frame = self.scheduled_frame.lock().unwrap();
|
||||
if let Some(scheduled_frame) = *next_scheduled_frame {
|
||||
if scheduled_frame < Instant::now() {
|
||||
*next_scheduled_frame = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,322 +1,362 @@
|
||||
mod animation_utils;
|
||||
mod cursor_vfx;
|
||||
mod blink;
|
||||
|
||||
use skulpin::skia_safe::{Canvas, Paint, Path, Point};
|
||||
|
||||
use crate::settings::*;
|
||||
use crate::renderer::CachingShaper;
|
||||
use crate::editor::{EDITOR, Colors, Cursor, CursorShape};
|
||||
use crate::redraw_scheduler::REDRAW_SCHEDULER;
|
||||
|
||||
use animation_utils::*;
|
||||
use blink::*;
|
||||
|
||||
|
||||
const COMMAND_LINE_DELAY_FRAMES: u64 = 5;
|
||||
const DEFAULT_CELL_PERCENTAGE: f32 = 1.0 / 8.0;
|
||||
|
||||
const STANDARD_CORNERS: &[(f32, f32); 4] = &[(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)];
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorSettings {
|
||||
animation_length: f32,
|
||||
trail_size: f32,
|
||||
vfx_mode: cursor_vfx::VfxMode,
|
||||
vfx_opacity: f32,
|
||||
vfx_particle_lifetime: f32,
|
||||
vfx_particle_density: f32,
|
||||
vfx_particle_speed: f32,
|
||||
vfx_particle_phase: f32,
|
||||
vfx_particle_curl: f32,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
SETTINGS.set(&CursorSettings {
|
||||
animation_length: 0.13,
|
||||
trail_size: 0.7,
|
||||
vfx_mode: cursor_vfx::VfxMode::Disabled,
|
||||
vfx_opacity: 200.0,
|
||||
vfx_particle_lifetime: 1.2,
|
||||
vfx_particle_density: 7.0,
|
||||
vfx_particle_speed: 10.0,
|
||||
vfx_particle_phase: 1.5,
|
||||
vfx_particle_curl: 1.0,
|
||||
});
|
||||
|
||||
register_nvim_setting!("cursor_animation_length", CursorSettings::animation_length);
|
||||
register_nvim_setting!("cursor_trail_size", CursorSettings::trail_size);
|
||||
register_nvim_setting!("cursor_vfx_mode", CursorSettings::vfx_mode);
|
||||
register_nvim_setting!("cursor_vfx_opacity", CursorSettings::vfx_opacity);
|
||||
register_nvim_setting!("cursor_vfx_particle_lifetime", CursorSettings::vfx_particle_lifetime);
|
||||
register_nvim_setting!("cursor_vfx_particle_density", CursorSettings::vfx_particle_density);
|
||||
register_nvim_setting!("cursor_vfx_particle_speed", CursorSettings::vfx_particle_speed);
|
||||
register_nvim_setting!("cursor_vfx_particle_phase", CursorSettings::vfx_particle_phase);
|
||||
register_nvim_setting!("cursor_vfx_particle_curl", CursorSettings::vfx_particle_curl);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Corner {
|
||||
start_position: Point,
|
||||
current_position: Point,
|
||||
relative_position: Point,
|
||||
previous_destination: Point,
|
||||
t: f32,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
pub fn new() -> Corner {
|
||||
Corner {
|
||||
start_position: Point::new(0.0, 0.0),
|
||||
current_position: Point::new(0.0, 0.0),
|
||||
relative_position: Point::new(0.0, 0.0),
|
||||
previous_destination: Point::new(-1000.0, -1000.0),
|
||||
t: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, settings: &CursorSettings, font_dimensions: Point, destination: Point, dt: f32) -> bool {
|
||||
// Update destination if needed
|
||||
let mut immediate_movement = false;
|
||||
if destination != self.previous_destination {
|
||||
let travel_distance = destination - self.previous_destination;
|
||||
let chars_travel_x = travel_distance.x / font_dimensions.x;
|
||||
if travel_distance.y == 0.0 && (chars_travel_x - 1.0).abs() < 0.1 {
|
||||
// We're moving one character to the right. Make movement immediate to avoid lag
|
||||
// while typing
|
||||
immediate_movement = true;
|
||||
}
|
||||
self.t = 0.0;
|
||||
self.start_position = self.current_position;
|
||||
self.previous_destination = destination;
|
||||
}
|
||||
|
||||
// Check first if animation's over
|
||||
if self.t > 1.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate window-space destination for corner
|
||||
let relative_scaled_position: Point = (
|
||||
self.relative_position.x * font_dimensions.x,
|
||||
self.relative_position.y * font_dimensions.y,
|
||||
).into();
|
||||
|
||||
let corner_destination = destination + relative_scaled_position;
|
||||
|
||||
if immediate_movement {
|
||||
self.t = 1.0;
|
||||
self.current_position = corner_destination;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate how much a corner will be lagging behind based on how much it's aligned
|
||||
// with the direction of motion. Corners in front will move faster than corners in the
|
||||
// back
|
||||
let travel_direction = {
|
||||
let mut d = destination - self.current_position;
|
||||
d.normalize();
|
||||
d
|
||||
};
|
||||
|
||||
let corner_direction = {
|
||||
let mut d = self.relative_position;
|
||||
d.normalize();
|
||||
d
|
||||
};
|
||||
|
||||
let direction_alignment = travel_direction.dot(corner_direction);
|
||||
|
||||
|
||||
if self.t == 1.0 {
|
||||
// We are at destination, move t out of 0-1 range to stop the animation
|
||||
self.t = 2.0;
|
||||
} else {
|
||||
let corner_dt = dt * lerp(1.0, 1.0 - settings.trail_size, -direction_alignment);
|
||||
self.t = (self.t + corner_dt / settings.animation_length).min(1.0)
|
||||
}
|
||||
|
||||
self.current_position =
|
||||
ease_point(ease_out_expo, self.start_position, corner_destination, self.t);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CursorRenderer {
|
||||
pub corners: Vec<Corner>,
|
||||
pub previous_position: (u64, u64),
|
||||
pub command_line_delay: u64,
|
||||
blink_status: BlinkStatus,
|
||||
previous_cursor_shape: Option<CursorShape>,
|
||||
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
|
||||
previous_vfx_mode: cursor_vfx::VfxMode,
|
||||
}
|
||||
|
||||
impl CursorRenderer {
|
||||
pub fn new() -> CursorRenderer {
|
||||
let mut renderer = CursorRenderer {
|
||||
corners: vec![Corner::new(); 4],
|
||||
previous_position: (0, 0),
|
||||
command_line_delay: 0,
|
||||
blink_status: BlinkStatus::new(),
|
||||
previous_cursor_shape: None,
|
||||
//cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)),
|
||||
cursor_vfx: None,
|
||||
previous_vfx_mode: cursor_vfx::VfxMode::Disabled,
|
||||
};
|
||||
renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE);
|
||||
renderer
|
||||
}
|
||||
|
||||
fn set_cursor_shape(&mut self, cursor_shape: &CursorShape, cell_percentage: f32) {
|
||||
self.corners = self.corners
|
||||
.clone()
|
||||
.into_iter().enumerate()
|
||||
.map(|(i, corner)| {
|
||||
let (x, y) = STANDARD_CORNERS[i];
|
||||
Corner {
|
||||
relative_position: match cursor_shape {
|
||||
CursorShape::Block => (x, y).into(),
|
||||
// Transform the x position so that the right side is translated over to
|
||||
// the BAR_WIDTH position
|
||||
CursorShape::Vertical => ((x + 0.5) * cell_percentage - 0.5, y).into(),
|
||||
// Do the same as above, but flip the y coordinate and then flip the result
|
||||
// so that the horizontal bar is at the bottom of the character space
|
||||
// instead of the top.
|
||||
CursorShape::Horizontal => (x, -((-y + 0.5) * cell_percentage - 0.5)).into()
|
||||
},
|
||||
t: 0.0,
|
||||
start_position: corner.current_position,
|
||||
.. corner
|
||||
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Corner>>();
|
||||
}
|
||||
|
||||
pub fn draw(&mut self,
|
||||
cursor: Cursor, default_colors: &Colors,
|
||||
font_width: f32, font_height: f32,
|
||||
shaper: &mut CachingShaper, canvas: &mut Canvas,
|
||||
dt: f32) {
|
||||
let render = self.blink_status.update_status(&cursor);
|
||||
|
||||
let settings = SETTINGS.get::<CursorSettings>();
|
||||
|
||||
if settings.vfx_mode != self.previous_vfx_mode {
|
||||
self.cursor_vfx = cursor_vfx::new_cursor_vfx(&settings.vfx_mode);
|
||||
self.previous_vfx_mode = settings.vfx_mode.clone();
|
||||
}
|
||||
|
||||
let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None);
|
||||
paint.set_anti_alias(true);
|
||||
|
||||
self.previous_position = {
|
||||
let editor = EDITOR.lock();
|
||||
let (_, grid_y) = cursor.position;
|
||||
let (_, previous_y) = self.previous_position;
|
||||
if grid_y == editor.grid.height - 1 && previous_y != grid_y {
|
||||
self.command_line_delay += 1;
|
||||
if self.command_line_delay < COMMAND_LINE_DELAY_FRAMES {
|
||||
self.previous_position
|
||||
} else {
|
||||
self.command_line_delay = 0;
|
||||
cursor.position
|
||||
}
|
||||
} else {
|
||||
self.command_line_delay = 0;
|
||||
cursor.position
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let (grid_x, grid_y) = self.previous_position;
|
||||
|
||||
let (character, font_dimensions): (String, Point) = {
|
||||
let editor = EDITOR.lock();
|
||||
let character = match editor.grid.get_cell(grid_x, grid_y) {
|
||||
Some(Some((character, _))) => character.clone(),
|
||||
_ => ' '.to_string(),
|
||||
};
|
||||
|
||||
let is_double = match editor.grid.get_cell(grid_x + 1, grid_y) {
|
||||
Some(Some((character, _))) => character.is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let font_width = match (is_double, &cursor.shape) {
|
||||
(true, CursorShape::Block) => font_width * 2.0,
|
||||
_ => font_width
|
||||
};
|
||||
(character, (font_width, font_height).into())
|
||||
};
|
||||
let destination: Point = (grid_x as f32 * font_width, grid_y as f32 * font_height).into();
|
||||
let center_destination = destination + font_dimensions * 0.5;
|
||||
|
||||
let new_cursor = Some(cursor.shape.clone());
|
||||
|
||||
if self.previous_cursor_shape != new_cursor {
|
||||
self.previous_cursor_shape = new_cursor;
|
||||
self.set_cursor_shape(&cursor.shape, cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE));
|
||||
|
||||
if let Some(vfx) = self.cursor_vfx.as_mut() {
|
||||
vfx.restart(center_destination);
|
||||
}
|
||||
}
|
||||
|
||||
let mut animating = false;
|
||||
if !center_destination.is_zero() {
|
||||
for corner in self.corners.iter_mut() {
|
||||
let corner_animating = corner.update(&settings, font_dimensions, center_destination, dt);
|
||||
animating |= corner_animating;
|
||||
}
|
||||
|
||||
let vfx_animating = if let Some(vfx) = self.cursor_vfx.as_mut() {
|
||||
vfx.update(&settings, center_destination, (font_width, font_height), dt)
|
||||
}else{
|
||||
false
|
||||
};
|
||||
|
||||
animating |= vfx_animating;
|
||||
}
|
||||
|
||||
if animating || self.command_line_delay != 0 {
|
||||
REDRAW_SCHEDULER.queue_next_frame();
|
||||
}
|
||||
|
||||
if cursor.enabled && render {
|
||||
// Draw Background
|
||||
paint.set_color(cursor.background(&default_colors).to_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);
|
||||
|
||||
// Draw foreground
|
||||
paint.set_color(cursor.foreground(&default_colors).to_color());
|
||||
canvas.save();
|
||||
canvas.clip_path(&path, None, Some(false));
|
||||
|
||||
let blobs = &shaper.shape_cached(&character, false, false);
|
||||
for blob in blobs.iter() {
|
||||
canvas.draw_text_blob(&blob, destination, &paint);
|
||||
}
|
||||
canvas.restore();
|
||||
if let Some(vfx) = self.cursor_vfx.as_ref() {
|
||||
vfx.render(&settings, canvas, &cursor, &default_colors, (font_width, font_height));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
mod animation_utils;
|
||||
mod blink;
|
||||
mod cursor_vfx;
|
||||
|
||||
use skulpin::skia_safe::{Canvas, Paint, Path, Point};
|
||||
|
||||
use crate::editor::{Colors, Cursor, CursorShape, EDITOR};
|
||||
use crate::redraw_scheduler::REDRAW_SCHEDULER;
|
||||
use crate::renderer::CachingShaper;
|
||||
use crate::settings::*;
|
||||
|
||||
use animation_utils::*;
|
||||
use blink::*;
|
||||
|
||||
const COMMAND_LINE_DELAY_FRAMES: u64 = 5;
|
||||
const DEFAULT_CELL_PERCENTAGE: f32 = 1.0 / 8.0;
|
||||
|
||||
const STANDARD_CORNERS: &[(f32, f32); 4] = &[(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)];
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorSettings {
|
||||
animation_length: f32,
|
||||
trail_size: f32,
|
||||
vfx_mode: cursor_vfx::VfxMode,
|
||||
vfx_opacity: f32,
|
||||
vfx_particle_lifetime: f32,
|
||||
vfx_particle_density: f32,
|
||||
vfx_particle_speed: f32,
|
||||
vfx_particle_phase: f32,
|
||||
vfx_particle_curl: f32,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
SETTINGS.set(&CursorSettings {
|
||||
animation_length: 0.13,
|
||||
trail_size: 0.7,
|
||||
vfx_mode: cursor_vfx::VfxMode::Disabled,
|
||||
vfx_opacity: 200.0,
|
||||
vfx_particle_lifetime: 1.2,
|
||||
vfx_particle_density: 7.0,
|
||||
vfx_particle_speed: 10.0,
|
||||
vfx_particle_phase: 1.5,
|
||||
vfx_particle_curl: 1.0,
|
||||
});
|
||||
|
||||
register_nvim_setting!("cursor_animation_length", CursorSettings::animation_length);
|
||||
register_nvim_setting!("cursor_trail_size", CursorSettings::trail_size);
|
||||
register_nvim_setting!("cursor_vfx_mode", CursorSettings::vfx_mode);
|
||||
register_nvim_setting!("cursor_vfx_opacity", CursorSettings::vfx_opacity);
|
||||
register_nvim_setting!(
|
||||
"cursor_vfx_particle_lifetime",
|
||||
CursorSettings::vfx_particle_lifetime
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"cursor_vfx_particle_density",
|
||||
CursorSettings::vfx_particle_density
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"cursor_vfx_particle_speed",
|
||||
CursorSettings::vfx_particle_speed
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"cursor_vfx_particle_phase",
|
||||
CursorSettings::vfx_particle_phase
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"cursor_vfx_particle_curl",
|
||||
CursorSettings::vfx_particle_curl
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Corner {
|
||||
start_position: Point,
|
||||
current_position: Point,
|
||||
relative_position: Point,
|
||||
previous_destination: Point,
|
||||
t: f32,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
pub fn new() -> Corner {
|
||||
Corner {
|
||||
start_position: Point::new(0.0, 0.0),
|
||||
current_position: Point::new(0.0, 0.0),
|
||||
relative_position: Point::new(0.0, 0.0),
|
||||
previous_destination: Point::new(-1000.0, -1000.0),
|
||||
t: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
settings: &CursorSettings,
|
||||
font_dimensions: Point,
|
||||
destination: Point,
|
||||
dt: f32,
|
||||
) -> bool {
|
||||
// Update destination if needed
|
||||
let mut immediate_movement = false;
|
||||
if destination != self.previous_destination {
|
||||
let travel_distance = destination - self.previous_destination;
|
||||
let chars_travel_x = travel_distance.x / font_dimensions.x;
|
||||
if travel_distance.y == 0.0 && (chars_travel_x - 1.0).abs() < 0.1 {
|
||||
// We're moving one character to the right. Make movement immediate to avoid lag
|
||||
// while typing
|
||||
immediate_movement = true;
|
||||
}
|
||||
self.t = 0.0;
|
||||
self.start_position = self.current_position;
|
||||
self.previous_destination = destination;
|
||||
}
|
||||
|
||||
// Check first if animation's over
|
||||
if self.t > 1.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate window-space destination for corner
|
||||
let relative_scaled_position: Point = (
|
||||
self.relative_position.x * font_dimensions.x,
|
||||
self.relative_position.y * font_dimensions.y,
|
||||
)
|
||||
.into();
|
||||
|
||||
let corner_destination = destination + relative_scaled_position;
|
||||
|
||||
if immediate_movement {
|
||||
self.t = 1.0;
|
||||
self.current_position = corner_destination;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate how much a corner will be lagging behind based on how much it's aligned
|
||||
// with the direction of motion. Corners in front will move faster than corners in the
|
||||
// back
|
||||
let travel_direction = {
|
||||
let mut d = destination - self.current_position;
|
||||
d.normalize();
|
||||
d
|
||||
};
|
||||
|
||||
let corner_direction = {
|
||||
let mut d = self.relative_position;
|
||||
d.normalize();
|
||||
d
|
||||
};
|
||||
|
||||
let direction_alignment = travel_direction.dot(corner_direction);
|
||||
|
||||
if self.t == 1.0 {
|
||||
// We are at destination, move t out of 0-1 range to stop the animation
|
||||
self.t = 2.0;
|
||||
} else {
|
||||
let corner_dt = dt * lerp(1.0, 1.0 - settings.trail_size, -direction_alignment);
|
||||
self.t = (self.t + corner_dt / settings.animation_length).min(1.0)
|
||||
}
|
||||
|
||||
self.current_position = ease_point(
|
||||
ease_out_expo,
|
||||
self.start_position,
|
||||
corner_destination,
|
||||
self.t,
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CursorRenderer {
|
||||
pub corners: Vec<Corner>,
|
||||
pub previous_position: (u64, u64),
|
||||
pub command_line_delay: u64,
|
||||
blink_status: BlinkStatus,
|
||||
previous_cursor_shape: Option<CursorShape>,
|
||||
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
|
||||
previous_vfx_mode: cursor_vfx::VfxMode,
|
||||
}
|
||||
|
||||
impl CursorRenderer {
|
||||
pub fn new() -> CursorRenderer {
|
||||
let mut renderer = CursorRenderer {
|
||||
corners: vec![Corner::new(); 4],
|
||||
previous_position: (0, 0),
|
||||
command_line_delay: 0,
|
||||
blink_status: BlinkStatus::new(),
|
||||
previous_cursor_shape: None,
|
||||
//cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)),
|
||||
cursor_vfx: None,
|
||||
previous_vfx_mode: cursor_vfx::VfxMode::Disabled,
|
||||
};
|
||||
renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE);
|
||||
renderer
|
||||
}
|
||||
|
||||
fn set_cursor_shape(&mut self, cursor_shape: &CursorShape, cell_percentage: f32) {
|
||||
self.corners = self
|
||||
.corners
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, corner)| {
|
||||
let (x, y) = STANDARD_CORNERS[i];
|
||||
Corner {
|
||||
relative_position: match cursor_shape {
|
||||
CursorShape::Block => (x, y).into(),
|
||||
// Transform the x position so that the right side is translated over to
|
||||
// the BAR_WIDTH position
|
||||
CursorShape::Vertical => ((x + 0.5) * cell_percentage - 0.5, y).into(),
|
||||
// Do the same as above, but flip the y coordinate and then flip the result
|
||||
// so that the horizontal bar is at the bottom of the character space
|
||||
// instead of the top.
|
||||
CursorShape::Horizontal => {
|
||||
(x, -((-y + 0.5) * cell_percentage - 0.5)).into()
|
||||
}
|
||||
},
|
||||
t: 0.0,
|
||||
start_position: corner.current_position,
|
||||
..corner
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Corner>>();
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
cursor: Cursor,
|
||||
default_colors: &Colors,
|
||||
font_width: f32,
|
||||
font_height: f32,
|
||||
shaper: &mut CachingShaper,
|
||||
canvas: &mut Canvas,
|
||||
dt: f32,
|
||||
) {
|
||||
let render = self.blink_status.update_status(&cursor);
|
||||
|
||||
let settings = SETTINGS.get::<CursorSettings>();
|
||||
|
||||
if settings.vfx_mode != self.previous_vfx_mode {
|
||||
self.cursor_vfx = cursor_vfx::new_cursor_vfx(&settings.vfx_mode);
|
||||
self.previous_vfx_mode = settings.vfx_mode.clone();
|
||||
}
|
||||
|
||||
let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None);
|
||||
paint.set_anti_alias(true);
|
||||
|
||||
self.previous_position = {
|
||||
let editor = EDITOR.lock();
|
||||
let (_, grid_y) = cursor.position;
|
||||
let (_, previous_y) = self.previous_position;
|
||||
if grid_y == editor.grid.height - 1 && previous_y != grid_y {
|
||||
self.command_line_delay += 1;
|
||||
if self.command_line_delay < COMMAND_LINE_DELAY_FRAMES {
|
||||
self.previous_position
|
||||
} else {
|
||||
self.command_line_delay = 0;
|
||||
cursor.position
|
||||
}
|
||||
} else {
|
||||
self.command_line_delay = 0;
|
||||
cursor.position
|
||||
}
|
||||
};
|
||||
|
||||
let (grid_x, grid_y) = self.previous_position;
|
||||
|
||||
let (character, font_dimensions): (String, Point) = {
|
||||
let editor = EDITOR.lock();
|
||||
let character = match editor.grid.get_cell(grid_x, grid_y) {
|
||||
Some(Some((character, _))) => character.clone(),
|
||||
_ => ' '.to_string(),
|
||||
};
|
||||
|
||||
let is_double = match editor.grid.get_cell(grid_x + 1, grid_y) {
|
||||
Some(Some((character, _))) => character.is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let font_width = match (is_double, &cursor.shape) {
|
||||
(true, CursorShape::Block) => font_width * 2.0,
|
||||
_ => font_width,
|
||||
};
|
||||
(character, (font_width, font_height).into())
|
||||
};
|
||||
let destination: Point = (grid_x as f32 * font_width, grid_y as f32 * font_height).into();
|
||||
let center_destination = destination + font_dimensions * 0.5;
|
||||
|
||||
let new_cursor = Some(cursor.shape.clone());
|
||||
|
||||
if self.previous_cursor_shape != new_cursor {
|
||||
self.previous_cursor_shape = new_cursor;
|
||||
self.set_cursor_shape(
|
||||
&cursor.shape,
|
||||
cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE),
|
||||
);
|
||||
|
||||
if let Some(vfx) = self.cursor_vfx.as_mut() {
|
||||
vfx.restart(center_destination);
|
||||
}
|
||||
}
|
||||
|
||||
let mut animating = false;
|
||||
if !center_destination.is_zero() {
|
||||
for corner in self.corners.iter_mut() {
|
||||
let corner_animating =
|
||||
corner.update(&settings, font_dimensions, center_destination, dt);
|
||||
animating |= corner_animating;
|
||||
}
|
||||
|
||||
let vfx_animating = if let Some(vfx) = self.cursor_vfx.as_mut() {
|
||||
vfx.update(&settings, center_destination, (font_width, font_height), dt)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
animating |= vfx_animating;
|
||||
}
|
||||
|
||||
if animating || self.command_line_delay != 0 {
|
||||
REDRAW_SCHEDULER.queue_next_frame();
|
||||
}
|
||||
|
||||
if cursor.enabled && render {
|
||||
// Draw Background
|
||||
paint.set_color(cursor.background(&default_colors).to_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);
|
||||
|
||||
// Draw foreground
|
||||
paint.set_color(cursor.foreground(&default_colors).to_color());
|
||||
canvas.save();
|
||||
canvas.clip_path(&path, None, Some(false));
|
||||
|
||||
let blobs = &shaper.shape_cached(&character, false, false);
|
||||
for blob in blobs.iter() {
|
||||
canvas.draw_text_blob(&blob, destination, &paint);
|
||||
}
|
||||
canvas.restore();
|
||||
if let Some(vfx) = self.cursor_vfx.as_ref() {
|
||||
vfx.render(
|
||||
&settings,
|
||||
canvas,
|
||||
&cursor,
|
||||
&default_colors,
|
||||
(font_width, font_height),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue