progress toward refactor

macos-click-through
keith 4 years ago
parent 494f9a395c
commit 4fd863ca2c

@ -5,9 +5,7 @@ use std::fmt;
use rmpv::Value;
use skulpin::skia_safe::Color4f;
use crate::editor::EDITOR;
use crate::editor::{Colors, CursorMode, CursorShape, Style};
use crate::error_handling::ResultPanicExplanation;
#[derive(Debug, Clone)]
pub enum ParseError {
@ -610,7 +608,7 @@ fn parse_grid_destroy(grid_destroy_arguments: Vec<Value>) -> Result<RedrawEvent>
}
fn parse_grid_cursor_goto(cursor_goto_arguments: Vec<Value>) -> Result<RedrawEvent> {
let [grid_id, column, row] =
let [grid_id, row, column] =
extract_values(cursor_goto_arguments, [Value::Nil, Value::Nil, Value::Nil])?;
Ok(RedrawEvent::CursorGoto {
@ -1030,15 +1028,3 @@ pub fn parse_channel_list(channel_infos: Vec<Value>) -> Result<Vec<ChannelInfo>>
.map(parse_channel_info)
.collect::<Result<Vec<ChannelInfo>>>()
}
pub(super) fn handle_redraw_event_group(arguments: Vec<Value>) {
for events in arguments {
let parsed_events = parse_redraw_event(events)
.unwrap_or_explained_panic("Could not parse event from neovim");
for parsed_event in parsed_events {
let mut editor = EDITOR.lock();
editor.handle_redraw_event(parsed_event);
}
}
}

@ -1,17 +1,34 @@
use std::sync::Arc;
use std::sync::mpsc::Sender;
use async_trait::async_trait;
use log::trace;
use nvim_rs::{compat::tokio::Compat, Handler, Neovim};
use rmpv::Value;
use tokio::sync::mpsc::UnboundedSender;
use tokio::process::ChildStdin;
use tokio::task;
use parking_lot::Mutex;
use super::events::handle_redraw_event_group;
use super::events::{parse_redraw_event, RedrawEvent};
use super::ui_commands::UiCommand;
use super::BRIDGE;
use crate::settings::SETTINGS;
use crate::error_handling::ResultPanicExplanation;
#[derive(Clone)]
pub struct NeovimHandler();
pub struct NeovimHandler {
ui_command_sender: Arc<Mutex<UnboundedSender<UiCommand>>>,
redraw_event_sender: Arc<Mutex<Sender<RedrawEvent>>>
}
impl NeovimHandler {
pub fn new(ui_command_sender: UnboundedSender<UiCommand>, redraw_event_sender: Sender<RedrawEvent>) -> NeovimHandler {
NeovimHandler {
ui_command_sender: Arc::new(Mutex::new(ui_command_sender)),
redraw_event_sender: Arc::new(Mutex::new(redraw_event_sender))
}
}
}
#[async_trait]
impl Handler for NeovimHandler {
@ -24,20 +41,33 @@ impl Handler for NeovimHandler {
_neovim: Neovim<Compat<ChildStdin>>,
) {
trace!("Neovim notification: {:?}", &event_name);
let ui_command_sender = self.ui_command_sender.clone();
let redraw_event_sender = self.redraw_event_sender.clone();
task::spawn_blocking(move || match event_name.as_ref() {
"redraw" => {
handle_redraw_event_group(arguments);
for events in arguments {
let parsed_events = parse_redraw_event(events)
.unwrap_or_explained_panic("Could not parse event from neovim");
for parsed_event in parsed_events {
let redraw_event_sender = redraw_event_sender.lock();
redraw_event_sender.send(parsed_event).ok();
}
}
}
"setting_changed" => {
SETTINGS.handle_changed_notification(arguments);
}
#[cfg(windows)]
"neovide.register_right_click" => {
BRIDGE.queue_command(UiCommand::RegisterRightClick);
let ui_command_sender = ui_command_sender.lock();
ui_command_sender.send(UiCommand::RegisterRightClick).ok();
}
#[cfg(windows)]
"neovide.unregister_right_click" => {
BRIDGE.queue_command(UiCommand::UnregisterRightClick);
let ui_command_sender = ui_command_sender.lock();
ui_command_sender.send(UiCommand::UnregisterRightClick).ok();
}
_ => {}
})

@ -8,13 +8,17 @@ mod ui_commands;
use std::process::Stdio;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::mpsc::Sender;
use std::env;
use std::path::Path;
use std::thread;
use log::{error, info, trace, warn};
use log::{error, info, warn};
use nvim_rs::{create::tokio as create, UiAttachOptions};
use rmpv::Value;
use tokio::process::Command;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::process::Command;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::error_handling::ResultPanicExplanation;
use crate::settings::*;
@ -22,14 +26,8 @@ use crate::window::window_geometry_or_default;
pub use events::*;
use handler::NeovimHandler;
pub use layouts::*;
use std::env;
use std::path::Path;
pub use ui_commands::UiCommand;
lazy_static! {
pub static ref BRIDGE: Bridge = Bridge::new();
}
#[cfg(windows)]
fn set_windows_creation_flags(cmd: &mut Command) {
cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
@ -112,25 +110,14 @@ pub fn create_nvim_command() -> Command {
cmd
}
async fn drain(receiver: &mut UnboundedReceiver<UiCommand>) -> Option<Vec<UiCommand>> {
if let Some(ui_command) = receiver.recv().await {
let mut results = vec![ui_command];
while let Ok(ui_command) = receiver.try_recv() {
results.push(ui_command);
}
Some(results)
} else {
None
}
}
async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
async fn start_process(ui_command_sender: UnboundedSender<UiCommand>, mut ui_command_receiver: UnboundedReceiver<UiCommand>, redraw_event_sender: Sender<RedrawEvent>, running: Arc<AtomicBool>) {
let (width, height) = window_geometry_or_default();
let (mut nvim, io_handler, _) =
create::new_child_cmd(&mut create_nvim_command(), NeovimHandler())
create::new_child_cmd(&mut create_nvim_command(), NeovimHandler::new(ui_command_sender, redraw_event_sender))
.await
.unwrap_or_explained_panic("Could not locate or start the neovim process");
let close_watcher_running = running.clone();
tokio::spawn(async move {
info!("Close watcher started");
match io_handler.await {
@ -142,7 +129,7 @@ async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
}
Ok(Ok(())) => {}
};
BRIDGE.running.store(false, Ordering::Relaxed);
close_watcher_running.store(false, Ordering::Relaxed);
});
if let Ok(Value::Integer(correct_version)) = nvim.eval("has(\"nvim-0.4\")").await {
@ -233,31 +220,16 @@ async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
let nvim = Arc::new(nvim);
let input_nvim = nvim.clone();
let ui_command_processor_running = running.clone();
tokio::spawn(async move {
info!("UiCommand processor started");
while let Some(commands) = drain(&mut receiver).await {
if !BRIDGE.running.load(Ordering::Relaxed) {
while let Some(ui_command) = ui_command_receiver.recv().await {
if !ui_command_processor_running.load(Ordering::Relaxed) {
return;
}
let (resize_list, other_commands): (Vec<UiCommand>, Vec<UiCommand>) = commands
.into_iter()
.partition(|command| command.is_resize());
for command in resize_list
.into_iter()
.last()
.into_iter()
.chain(other_commands.into_iter())
{
let input_nvim = input_nvim.clone();
tokio::spawn(async move {
if !BRIDGE.running.load(Ordering::Relaxed) {
return;
}
trace!("Executing UiCommand: {:?}", &command);
command.execute(&input_nvim).await;
});
}
ui_command.execute(&input_nvim).await;
}
});
@ -269,34 +241,16 @@ async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
.ok();
}
pub struct Bridge {
_runtime: Runtime, // Necessary to keep runtime running
sender: UnboundedSender<UiCommand>,
pub running: AtomicBool,
}
impl Bridge {
pub fn new() -> Bridge {
pub fn start_bridge(ui_command_sender: UnboundedSender<UiCommand>, ui_command_receiver: UnboundedReceiver<UiCommand>, redraw_event_sender: Sender<RedrawEvent>, running: Arc<AtomicBool>) {
thread::spawn(move || {
let runtime = Runtime::new().unwrap();
let (sender, receiver) = unbounded_channel::<UiCommand>();
let running_clone = running.clone();
runtime.spawn(async move {
start_process(receiver).await;
start_process(ui_command_sender, ui_command_receiver, redraw_event_sender, running_clone).await;
});
Bridge {
_runtime: runtime,
sender,
running: AtomicBool::new(true),
}
}
pub fn queue_command(&self, command: UiCommand) {
if !BRIDGE.running.load(Ordering::Relaxed) {
return;
}
trace!("UiCommand queued: {:?}", &command);
self.sender.send(command).unwrap_or_explained_panic(
"Could not send UI command from the window system to the neovim process.",
);
while running.load(Ordering::Relaxed) {
}
});
}

@ -3,7 +3,6 @@ use nvim_rs::compat::tokio::Compat;
use nvim_rs::Neovim;
use tokio::process::ChildStdin;
use crate::editor::EDITOR;
#[cfg(windows)]
use crate::settings::windows_registry::{
register_rightclick_directory, register_rightclick_file, unregister_rightclick,
@ -56,33 +55,27 @@ impl UiCommand {
grid_id,
position: (grid_x, grid_y),
} => {
if EDITOR.lock().mouse_enabled {
nvim.input_mouse("left", &action, "", grid_id as i64, grid_y as i64, grid_x as i64)
.await
.expect("Mouse Input Failed");
}
}
UiCommand::Scroll {
direction,
grid_id,
position: (grid_x, grid_y),
} => {
if EDITOR.lock().mouse_enabled {
nvim.input_mouse("wheel", &direction, "", grid_id as i64, grid_y as i64, grid_x as i64)
.await
.expect("Mouse Scroll Failed");
}
}
UiCommand::Drag {
grid_id,
position: (grid_x, grid_y)
} => {
if EDITOR.lock().mouse_enabled {
nvim.input_mouse("left", "drag", "", grid_id as i64, 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

@ -3,12 +3,12 @@ mod grid;
mod style;
mod window;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::mpsc::{Sender, Receiver};
use std::thread;
use log::{error, trace};
use parking_lot::Mutex;
use skulpin::skia_safe::colors;
use crate::bridge::{EditorMode, GuiOption, RedrawEvent, WindowAnchor};
use crate::redraw_scheduler::REDRAW_SCHEDULER;
@ -17,107 +17,121 @@ pub use grid::CharacterGrid;
pub use style::{Colors, Style};
pub use window::*;
lazy_static! {
pub static ref EDITOR: Arc<Mutex<Editor>> = Arc::new(Mutex::new(Editor::new()));
pub struct AnchorInfo {
pub anchor_grid_id: u64,
pub anchor_type: WindowAnchor,
pub anchor_left: f64,
pub anchor_top: f64,
}
pub struct RenderInfo {
pub windows: Vec<WindowRenderInfo>,
pub closed_window_ids: Vec<u64>,
impl WindowAnchor {
fn modified_top_left(&self, grid_left: f64, grid_top: f64, width: u64, height: u64) -> (f64, f64) {
match self {
WindowAnchor::NorthWest => (
grid_left,
grid_top,
),
WindowAnchor::NorthEast => (
grid_left - width as f64,
grid_top,
),
WindowAnchor::SouthWest => (
grid_left,
grid_top - height as f64,
),
WindowAnchor::SouthEast => (
grid_left - width as f64,
grid_top - height as f64,
),
}
}
}
pub struct WindowRenderInfo {
pub grid_id: u64,
pub grid_position: (f64, f64),
pub width: u64,
pub height: u64,
pub floating: bool,
pub draw_commands: Vec<DrawCommand>
pub enum DrawCommand {
CloseWindow(u64),
Window {
grid_id: u64,
command: WindowDrawCommand
},
UpdateCursor(Cursor),
FontChanged(String),
DefaultStyleChanged(Style),
TitleChanged(String),
SetMouseEnabled(bool),
}
pub struct Editor {
pub title: String,
pub windows: HashMap<u64, Window>,
pub closed_window_ids: HashSet<u64>,
pub mouse_enabled: bool,
pub guifont: Option<String>,
pub cursor: Cursor,
pub default_style: Arc<Style>,
pub defined_styles: HashMap<u64, Arc<Style>>,
pub previous_style: Option<Arc<Style>>,
pub mode_list: Vec<CursorMode>,
pub current_mode: EditorMode,
pub draw_command_sender: Sender<DrawCommand>
}
impl Editor {
pub fn new() -> Editor {
pub fn new(draw_command_sender: Sender<DrawCommand>) -> Editor {
Editor {
title: "Neovide".to_string(),
windows: HashMap::new(),
closed_window_ids: HashSet::new(),
mouse_enabled: true,
guifont: None,
cursor: Cursor::new(),
default_style: Arc::new(Style::new(Colors::new(
Some(colors::WHITE),
Some(colors::BLACK),
Some(colors::GREY),
))),
defined_styles: HashMap::new(),
previous_style: None,
mode_list: Vec::new(),
current_mode: EditorMode::Unknown(String::from("")),
draw_command_sender
}
}
pub fn handle_redraw_event(&mut self, event: RedrawEvent) {
match event {
RedrawEvent::SetTitle { title } => self.title = title,
RedrawEvent::SetTitle { title } => {
self.draw_command_sender.send(DrawCommand::TitleChanged(title)).ok();
},
RedrawEvent::ModeInfoSet { cursor_modes } => self.mode_list = cursor_modes,
RedrawEvent::OptionSet { gui_option } => self.set_option(gui_option),
RedrawEvent::ModeChange { mode, mode_index } => {
if let Some(cursor_mode) = self.mode_list.get(mode_index as usize) {
self.cursor.change_mode(cursor_mode, &self.defined_styles);
self.current_mode = mode
self.current_mode = mode;
self.draw_command_sender.send(DrawCommand::UpdateCursor(self.cursor.clone())).ok();
}
}
RedrawEvent::MouseOn => {
self.mouse_enabled = true;
self.draw_command_sender.send(DrawCommand::SetMouseEnabled(true)).ok();
}
RedrawEvent::MouseOff => {
self.mouse_enabled = false;
self.draw_command_sender.send(DrawCommand::SetMouseEnabled(false)).ok();
}
RedrawEvent::BusyStart => {
trace!("Cursor off");
self.cursor.enabled = false;
self.draw_command_sender.send(DrawCommand::UpdateCursor(self.cursor.clone())).ok();
}
RedrawEvent::BusyStop => {
trace!("Cursor on");
self.cursor.enabled = true;
self.draw_command_sender.send(DrawCommand::UpdateCursor(self.cursor.clone())).ok();
}
RedrawEvent::Flush => {
trace!("Image flushed");
REDRAW_SCHEDULER.queue_next_frame();
}
RedrawEvent::DefaultColorsSet { colors } => {
self.default_style = Arc::new(Style::new(colors))
self.draw_command_sender.send(DrawCommand::DefaultStyleChanged(Style::new(colors))).ok();
}
RedrawEvent::HighlightAttributesDefine { id, style } => {
self.defined_styles.insert(id, Arc::new(style));
}
RedrawEvent::CursorGoto { grid, row, column } => {
self.set_cursor_position(grid, row, column)
RedrawEvent::CursorGoto { grid, column: left, row: top } => {
self.set_cursor_position(grid, left, top)
}
RedrawEvent::Resize {
grid,
width,
height,
} => {
if let Some(window) = self.windows.get_mut(&grid) {
window.resize(width, height);
} else {
self.windows.insert(grid, Window::new(grid, width, height, None, WindowAnchor::NorthWest, 0.0, 0.0));
}
self.resize_window(grid, width, height);
}
RedrawEvent::GridLine {
grid,
@ -163,21 +177,21 @@ impl Editor {
start_column,
width,
height,
} => self.set_window_position(grid, start_row, start_column, width, height),
} => self.set_window_position(grid, start_column, start_row, width, height),
RedrawEvent::WindowFloatPosition {
grid,
anchor,
anchor_grid,
anchor_row,
anchor_column,
anchor_column: anchor_left,
anchor_row: anchor_top,
..
} => {
self.set_window_float_position(grid, anchor_grid, anchor, anchor_row, anchor_column)
self.set_window_float_position(grid, anchor_grid, anchor, anchor_left, anchor_top)
}
RedrawEvent::WindowHide { grid } => {
self.windows
.get_mut(&grid)
.map(|window| window.hidden = true);
.get(&grid)
.map(|window| window.hide());
}
RedrawEvent::WindowClose { grid } => self.close_window(grid),
RedrawEvent::MessageSetPosition { grid, row, .. } => {
@ -188,34 +202,55 @@ impl Editor {
}
fn close_window(&mut self, grid: u64) {
self.windows.remove(&grid);
self.closed_window_ids.insert(grid);
if let Some(window) = self.windows.remove(&grid) {
window.close();
self.draw_command_sender.send(DrawCommand::CloseWindow(grid)).ok();
}
}
fn resize_window(&mut self, grid: u64, width: u64, height: u64) {
println!("editor resize {}", grid);
if let Some(window) = self.windows.get_mut(&grid) {
window.resize(width, height);
} else {
let window = Window::new(
grid,
width,
height,
None,
0.0,
0.0,
self.draw_command_sender.clone());
self.windows.insert(grid, window);
}
}
fn set_window_position(
&mut self,
grid: u64,
start_row: u64,
start_column: u64,
start_left: u64,
start_top: u64,
width: u64,
height: u64,
) {
println!("position {}", grid);
if let Some(window) = self.windows.get_mut(&grid) {
window.hidden = false;
window.anchor_grid_id = None;
window.anchor_type = WindowAnchor::NorthWest;
window.anchor_row = start_row as f64;
window.anchor_column = start_column as f64;
window.resize(width, height);
window.position(
width,
height,
None,
start_left as f64,
start_top as f64);
window.show();
} else {
let new_window = Window::new(
grid,
width,
height,
None,
WindowAnchor::NorthWest,
start_row as f64,
start_column as f64,
start_left as f64,
start_top as f64,
self.draw_command_sender.clone()
);
self.windows.insert(grid, new_window);
}
@ -226,39 +261,56 @@ impl Editor {
grid: u64,
anchor_grid: u64,
anchor_type: WindowAnchor,
anchor_row: f64,
anchor_column: f64,
anchor_left: f64,
anchor_top: f64,
) {
println!("floating position {}", grid);
let parent_position = self.get_window_top_left(anchor_grid);
if let Some(window) = self.windows.get_mut(&grid) {
window.hidden = false;
window.anchor_grid_id = Some(anchor_grid);
window.anchor_type = anchor_type;
window.anchor_row = anchor_row;
window.anchor_column = anchor_column;
let width = window.get_width();
let height = window.get_height();
let (mut modified_left, mut modified_top) = anchor_type.modified_top_left(
anchor_left, anchor_top,
width, height);
if let Some((parent_left, parent_top)) = parent_position {
modified_left = parent_left + modified_left;
modified_top = parent_top + modified_top;
}
window.position(
width, height,
Some(AnchorInfo {
anchor_grid_id: anchor_grid,
anchor_type, anchor_left, anchor_top
}),
modified_left, modified_top);
window.show();
} else {
error!("Attempted to float window that does not exist.");
}
}
fn set_message_position(&mut self, grid: u64, row: u64) {
let parent_width = self.windows.get(&1).map(|parent| parent.grid.width).unwrap_or(1);
fn set_message_position(&mut self, grid: u64, grid_top: u64) {
println!("message position {}", grid);
let parent_width = self.windows.get(&1).map(|parent| parent.get_width()).unwrap_or(1);
if let Some(window) = self.windows.get_mut(&grid) {
window.hidden = false;
window.anchor_grid_id = Some(1);
window.anchor_type = WindowAnchor::NorthWest;
window.anchor_row = row as f64;
window.anchor_column = 0.0;
window.resize(parent_width, window.grid.height);
window.position(
parent_width, window.get_height(),
None,
0.0,
grid_top as f64);
window.show();
} else {
let new_window = Window::new(
grid,
parent_width,
1,
None,
WindowAnchor::NorthWest,
row as f64,
0.0,
grid_top as f64,
self.draw_command_sender.clone()
);
self.windows.insert(grid, new_window);
}
@ -266,111 +318,61 @@ impl Editor {
fn get_window_top_left(&self, grid: u64) -> Option<(f64, f64)> {
let window = self.windows.get(&grid)?;
let window_anchor_info = &window.anchor_info;
match window_anchor_info {
Some(anchor_info) => {
let (parent_anchor_left, parent_anchor_top) =
self.get_window_top_left(anchor_info.anchor_grid_id)?;
match window.anchor_grid_id {
Some(anchor_grid) => {
let (parent_anchor_column, parent_anchor_row) =
self.get_window_top_left(anchor_grid)?;
match window.anchor_type {
WindowAnchor::NorthWest => Some((
parent_anchor_column + window.anchor_column,
parent_anchor_row + window.anchor_row,
)),
WindowAnchor::NorthEast => Some((
parent_anchor_column + window.anchor_column - window.grid.width as f64,
parent_anchor_row + window.anchor_row,
)),
WindowAnchor::SouthWest => Some((
parent_anchor_column + window.anchor_column,
parent_anchor_row + window.anchor_row - window.grid.height as f64,
)),
WindowAnchor::SouthEast => Some((
parent_anchor_column + window.anchor_column - window.grid.width as f64,
parent_anchor_row + window.anchor_row - window.grid.height as f64,
)),
}
}
None => Some((window.anchor_column, window.anchor_row)),
let (anchor_modified_left, anchor_modified_top) = anchor_info.anchor_type.modified_top_left(
anchor_info.anchor_left, anchor_info.anchor_top,
window.get_width(), window.get_height());
Some((parent_anchor_left + anchor_modified_left, parent_anchor_top + anchor_modified_top))
}
None => Some(window.get_grid_position()),
}
}
fn set_cursor_position(&mut self, grid: u64, row: u64, column: u64) {
fn set_cursor_position(&mut self, grid: u64, grid_left: u64, grid_top: u64) {
match self.get_window_top_left(grid) {
Some((window_row, window_column)) => {
self.cursor.position = (window_row + row as f64, window_column + column as f64);
Some((window_left, window_top)) => {
self.cursor.position = (window_left + grid_left as f64, window_top + grid_top as f64);
if let Some(window) = self.windows.get(&grid) {
self.cursor.character = match window.grid.get_cell(row, column) {
Some(Some((character, _))) => character.clone(),
_ => ' '.to_string(),
};
self.cursor.double_width = match window.grid.get_cell(row, column + 1) {
Some(Some((character, _))) => character.is_empty(),
_ => false,
};
let (character, double_width) = window.get_cursor_character(grid_left, grid_top);
self.cursor.character = character;
self.cursor.double_width = double_width;
}
}
None => {
self.cursor.position = (row as f64, column as f64);
self.cursor.position = (grid_left as f64, grid_top as f64);
self.cursor.double_width = false;
self.cursor.character = " ".to_string();
}
}
self.draw_command_sender.send(DrawCommand::UpdateCursor(self.cursor.clone())).ok();
}
fn set_option(&mut self, gui_option: GuiOption) {
trace!("Option set {:?}", &gui_option);
if let GuiOption::GuiFont(guifont) = gui_option {
self.guifont = Some(guifont);
}
}
fn build_window_render_info(&mut self, grid_id: u64) -> Option<WindowRenderInfo> {
let grid_position = self.get_window_top_left(grid_id)?;
let window = self.windows.get_mut(&grid_id)?;
let draw_commands = window.build_draw_commands();
let width = window.grid.width;
let height = window.grid.height;
Some(WindowRenderInfo {
grid_id,
grid_position,
width,
height,
floating: window.anchor_grid_id.is_some(),
draw_commands
})
}
pub fn build_render_info(&mut self) -> RenderInfo {
let mut windows = Vec::new();
let window_ids: Vec<u64> = {
let (mut root_windows, mut floating_windows): (Vec<&Window>, Vec<&Window>) = self.windows
.values()
.filter(|window| !window.hidden)
.partition(|window| window.anchor_grid_id.is_none());
root_windows.sort_by(|window_a, window_b| window_a.grid_id.partial_cmp(&window_b.grid_id).unwrap());
floating_windows.sort_by(|window_a, window_b| window_a.grid_id.partial_cmp(&window_b.grid_id).unwrap());
root_windows.into_iter().chain(floating_windows.into_iter()).map(|window| window.grid_id).collect()
};
for window_id in window_ids.into_iter() {
if let Some(window_render_info) = self.build_window_render_info(window_id) {
windows.push(window_render_info);
self.draw_command_sender.send(DrawCommand::FontChanged(guifont)).ok();
}
}
}
let closed_window_ids = self.closed_window_ids.iter().copied().collect();
self.closed_window_ids.clear();
pub fn start_editor(redraw_event_receiver: Receiver<RedrawEvent>, draw_command_sender: Sender<DrawCommand>) {
thread::spawn(move || {
let mut editor = Editor::new(draw_command_sender);
RenderInfo {
windows,
closed_window_ids,
loop {
if let Ok(redraw_event) = redraw_event_receiver.recv() {
editor.handle_redraw_event(redraw_event);
} else {
break;
}
}
});
}

@ -1,20 +1,29 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::mem::swap;
use std::sync::mpsc::Sender;
use log::{trace, warn};
use log::warn;
use unicode_segmentation::UnicodeSegmentation;
use super::{DrawCommand, AnchorInfo};
use super::grid::CharacterGrid;
use super::style::Style;
use crate::bridge::{GridLineCell, WindowAnchor};
use crate::bridge::GridLineCell;
#[derive(new, Debug, Clone)]
pub enum DrawCommand {
pub enum WindowDrawCommand {
Position {
grid_left: f64,
grid_top: f64,
width: u64,
height: u64,
floating: bool
},
Cell {
text: String,
cell_width: u64,
grid_position: (u64, u64),
window_left: u64,
window_top: u64,
style: Option<Arc<Style>>,
},
Scroll {
@ -25,19 +34,22 @@ pub enum DrawCommand {
rows: i64,
cols: i64,
},
Resize,
Clear
Clear,
Show,
Hide,
Close
}
pub struct Window {
pub grid_id: u64,
pub grid: CharacterGrid,
pub hidden: bool,
pub anchor_grid_id: Option<u64>,
pub anchor_type: WindowAnchor,
pub anchor_row: f64,
pub anchor_column: f64,
pub queued_draw_commands: Vec<DrawCommand>
grid_id: u64,
grid: CharacterGrid,
pub anchor_info: Option<AnchorInfo>,
grid_left: f64,
grid_top: f64,
draw_command_sender: Sender<DrawCommand>
}
impl Window {
@ -45,31 +57,77 @@ impl Window {
grid_id: u64,
width: u64,
height: u64,
anchor_grid_id: Option<u64>,
anchor_type: WindowAnchor,
anchor_row: f64,
anchor_column: f64,
anchor_info: Option<AnchorInfo>,
grid_left: f64,
grid_top: f64,
draw_command_sender: Sender<DrawCommand>
) -> Window {
Window {
let window = Window {
grid_id,
anchor_grid_id,
anchor_type,
anchor_row,
anchor_column,
grid: CharacterGrid::new((width, height)),
hidden: false,
queued_draw_commands: Vec::new()
anchor_info,
grid_left,
grid_top,
draw_command_sender
};
window.send_updated_position();
window
}
fn send_command(&self, command: WindowDrawCommand) {
self.draw_command_sender.send(DrawCommand::Window {
grid_id: self.grid_id,
command
}).ok();
}
fn send_updated_position(&self) {
self.send_command(WindowDrawCommand::Position {
grid_left: self.grid_left,
grid_top: self.grid_top,
width: self.grid.width,
height: self.grid.height,
floating: self.anchor_info.is_some()
});
}
pub fn resize(&mut self, width: u64, height: u64) {
pub fn get_cursor_character(&self, window_left: u64, window_top: u64) -> (String, bool) {
let character = match self.grid.get_cell(window_left, window_top) {
Some(Some((character, _))) => character.clone(),
_ => ' '.to_string(),
};
let double_width = match self.grid.get_cell(window_left + 1, window_top) {
Some(Some((character, _))) => character.is_empty(),
_ => false,
};
(character, double_width)
}
pub fn get_width(&self) -> u64 {
self.grid.width
}
pub fn get_height(&self) -> u64 {
self.grid.height
}
pub fn get_grid_position(&self) -> (f64, f64) {
(self.grid_left, self.grid_top)
}
pub fn position(&mut self, width: u64, height: u64, anchor_info: Option<AnchorInfo>, grid_left: f64, grid_top: f64) {
self.grid.resize(width, height);
self.queued_draw_commands.push(DrawCommand::Resize)
self.anchor_info = anchor_info;
self.grid_left = grid_left;
self.grid_top = grid_top;
self.send_updated_position();
}
pub fn clear(&mut self) {
self.grid.clear();
self.queued_draw_commands.push(DrawCommand::Clear);
pub fn resize(&mut self, width: u64, height: u64) {
self.grid.resize(width, height);
self.send_updated_position();
}
fn draw_grid_line_cell(
@ -139,10 +197,11 @@ impl Window {
text.push_str(character);
}
self.queued_draw_commands.push(DrawCommand::Cell {
self.send_command(WindowDrawCommand::Cell {
text,
cell_width: draw_command_end_index - draw_command_start_index,
grid_position: (draw_command_start_index, row_index),
window_left: draw_command_start_index,
window_top: row_index,
style: style.clone()
});
@ -212,16 +271,25 @@ impl Window {
}
}
self.queued_draw_commands.push(DrawCommand::Scroll {
self.send_command(WindowDrawCommand::Scroll {
top, bot, left, right, rows, cols
});
}
pub fn build_draw_commands(&mut self) -> Vec<DrawCommand> {
let mut draw_commands = Vec::new();
swap(&mut self.queued_draw_commands, &mut draw_commands);
pub fn clear(&mut self) {
self.grid.clear();
self.send_command(WindowDrawCommand::Clear);
}
pub fn hide(&self) {
self.send_command(WindowDrawCommand::Hide);
}
pub fn show(&self) {
self.send_command(WindowDrawCommand::Show);
}
trace!("Draw commands sent");
draw_commands
pub fn close(&self) {
self.send_command(WindowDrawCommand::Close);
}
}

@ -18,16 +18,91 @@ extern crate rust_embed;
#[macro_use]
extern crate lazy_static;
use lazy_static::initialize;
use bridge::BRIDGE;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use std::process;
use window::ui_loop;
use tokio::sync::mpsc::unbounded_channel;
use window::window_geometry;
use bridge::start_bridge;
use editor::start_editor;
use window::start_window;
pub const INITIAL_DIMENSIONS: (u64, u64) = (100, 50);
fn main() {
// -----------
// | DATA FLOW |
// -----------
//
// Data flows in a circular motion via channels. This allows each component to handle and
// process data on their own thread while not blocking the other components from processing.
//
// This way Neovim continues to produce events, the window doesn't freeze and queues up ui
// commands, and the editor can do the processing necessary to handle the UI events
// effectively.
//
// BRIDGE
// V REDRAW EVENT
// EDITOR
// V DRAW COMMAND
// WINDOW
// V UI COMMAND
// BRIDGE
//
// BRIDGE:
// The bridge is responsible for the connection to the neovim process itself. It is in charge
// of starting and communicating to and from the process.
//
// REDRAW EVENT:
// Redraw events are direct events from the neovim process meant to specify how the editor
// should be drawn to the screen. They also include other things such as whether the mouse is
// enabled. The bridge takes these events, filters out some of them meant only for
// filtering, and forwards them to the editor.
//
// EDITOR:
// The editor is responsible for processing and transforming redraw events into something
// more readily renderable. Ligature support and multi window management requires some
// significant preprocessing of the redraw events in order to capture what exactly should get
// drawn where. Futher this step takes a bit of processing power to accomplish, so it is done
// on it's own thread. Ideally heavily computationally expensive tasks should be done in the
// editor.
//
// DRAW COMMAND:
// The draw commands are distilled render information describing actions to be done at the
// window by window level.
//
// WINDOW:
// The window is responsible for rendering and gathering input events from the user. This
// inncludes taking the draw commands from the editor and turning them into pixels on the
// screen. The ui commands are then forwarded back to the BRIDGE to convert them into
// commands for neovim to handle properly.
//
// UI COMMAND:
// The ui commands are things like text input/key bindings, outer window resizes, and mouse
// inputs.
//
// ------------------
// | Other Components |
// ------------------
//
// Neovide also includes some other systems which are globally available via lazy static
// instantiations.
//
// SETTINGS:
// The settings system is live updated from global variables in neovim with the prefix
// "neovide". They allow us to configure and manage the functionality of neovide from neovim
// init scripts and variables.
//
// REDRAW SCHEDULER:
// The redraw scheduler is a simple system in charge of deciding if the renderer should draw
// another frame next frame, or if it can safely skip drawing to save battery and cpu power.
// Multiple other parts of the app "queue_next_frame" function to ensure animations continue
// properly or updates to the graphics are pushed to the screen.
if let Err(err) = window_geometry() {
eprintln!("{}", err);
process::exit(1);
@ -50,12 +125,19 @@ fn main() {
}
}
bridge::layouts::initialize_settings();
window::initialize_settings();
redraw_scheduler::initialize_settings();
renderer::initialize_settings();
renderer::cursor_renderer::initialize_settings();
bridge::layouts::initialize_settings();
initialize(&BRIDGE);
ui_loop();
let running = Arc::new(AtomicBool::new(true));
let (redraw_event_sender, redraw_event_receiver) = channel();
let (draw_command_sender, draw_command_receiver) = channel();
let (ui_command_sender, ui_command_receiver) = unbounded_channel();
start_bridge(ui_command_sender.clone(), ui_command_receiver, redraw_event_sender, running.clone());
start_editor(redraw_event_receiver, draw_command_sender);
start_window(draw_command_receiver, ui_command_sender, running.clone());
}

@ -3,12 +3,11 @@ mod cursor_vfx;
use skulpin::skia_safe::{Canvas, Paint, Path, Point};
use crate::editor::{Colors, Cursor, CursorShape, EDITOR};
use crate::editor::{Colors, Cursor, CursorShape};
use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::renderer::CachingShaper;
use crate::renderer::animation_utils::*;
use crate::settings::*;
use crate::bridge::EditorMode;
use blink::*;
@ -178,6 +177,7 @@ impl Corner {
pub struct CursorRenderer {
pub corners: Vec<Corner>,
cursor: Cursor,
blink_status: BlinkStatus,
previous_cursor_shape: Option<CursorShape>,
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
@ -188,6 +188,7 @@ impl CursorRenderer {
pub fn new() -> CursorRenderer {
let mut renderer = CursorRenderer {
corners: vec![Corner::new(); 4],
cursor: Cursor::new(),
blink_status: BlinkStatus::new(),
previous_cursor_shape: None,
//cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)),
@ -198,6 +199,10 @@ impl CursorRenderer {
renderer
}
pub fn update_cursor(&mut self, new_cursor: Cursor) {
self.cursor = new_cursor;
}
fn set_cursor_shape(&mut self, cursor_shape: &CursorShape, cell_percentage: f32) {
self.corners = self
.corners
@ -230,7 +235,6 @@ impl CursorRenderer {
pub fn draw(
&mut self,
cursor: Cursor,
default_colors: &Colors,
font_size: (f32, f32),
shaper: &mut CachingShaper,
@ -238,7 +242,7 @@ impl CursorRenderer {
dt: f32,
) {
let (font_width, font_height) = font_size;
let render = self.blink_status.update_status(&cursor);
let render = self.blink_status.update_status(&self.cursor);
let settings = SETTINGS.get::<CursorSettings>();
if settings.vfx_mode != self.previous_vfx_mode {
@ -249,30 +253,31 @@ impl CursorRenderer {
let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None);
paint.set_anti_alias(settings.antialiasing);
let (grid_x, grid_y) = cursor.position;
let character = &cursor.character;
let (grid_x, grid_y) = self.cursor.position;
let character = self.cursor.character.clone();
let font_width = match (cursor.double_width, &cursor.shape) {
let font_width = match (self.cursor.double_width, &self.cursor.shape) {
(true, CursorShape::Block) => font_width * 2.0,
_ => font_width,
};
let font_dimensions: Point = (font_width, font_height).into();
let in_insert_mode = {
let editor = EDITOR.lock();
matches!(editor.current_mode, EditorMode::Insert)
};
let in_insert_mode = false;
// {
// let editor = EDITOR.lock();
// matches!(editor.current_mode, EditorMode::Insert)
// };
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());
let new_cursor = Some(self.cursor.shape.clone());
if self.previous_cursor_shape != new_cursor {
self.previous_cursor_shape = new_cursor;
self.previous_cursor_shape = new_cursor.clone();
self.set_cursor_shape(
&cursor.shape,
cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE),
&new_cursor.unwrap(),
self.cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE),
);
if let Some(vfx) = self.cursor_vfx.as_mut() {
@ -308,9 +313,9 @@ impl CursorRenderer {
REDRAW_SCHEDULER.queue_next_frame();
}
if cursor.enabled && render {
if self.cursor.enabled && render {
// Draw Background
paint.set_color(cursor.background(&default_colors).to_color());
paint.set_color(self.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.
@ -325,7 +330,7 @@ impl CursorRenderer {
canvas.draw_path(&path, &paint);
// Draw foreground
paint.set_color(cursor.foreground(&default_colors).to_color());
paint.set_color(self.cursor.foreground(&default_colors).to_color());
canvas.save();
canvas.clip_path(&path, None, Some(false));
@ -342,7 +347,7 @@ impl CursorRenderer {
vfx.render(
&settings,
canvas,
&cursor,
&self.cursor,
&default_colors,
(font_width, font_height),
);

@ -1,29 +1,25 @@
use std::collections::HashMap;
use std::sync::Arc;
use log::trace;
use skulpin::skia_safe::gpu::SurfaceOrigin;
use log::{trace, error};
use skulpin::skia_safe::{
colors, dash_path_effect, Budgeted, Canvas, ImageInfo, Paint, Rect, Surface, Point, BlendMode, image_filters::blur
};
use skulpin::skia_safe::canvas::{
SrcRectConstraint, SaveLayerRec
colors, dash_path_effect, Canvas, Paint, Rect, BlendMode
};
use skulpin::CoordinateSystemHelper;
mod caching_shaper;
mod rendered_window;
pub mod cursor_renderer;
pub mod font_options;
pub mod animation_utils;
pub use caching_shaper::CachingShaper;
pub use font_options::*;
use animation_utils::*;
use crate::editor::{Style, WindowRenderInfo, EDITOR, DrawCommand};
use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::editor::{Style, Colors, DrawCommand, WindowDrawCommand};
use crate::settings::*;
use cursor_renderer::CursorRenderer;
use rendered_window::RenderedWindow;
// ----------------------------------------------------------------------------
@ -49,91 +45,48 @@ pub fn initialize_settings() {
// ----------------------------------------------------------------------------
pub struct RenderedWindow {
surface: Surface,
start_position: Point,
current_position: Point,
previous_destination: Point,
t: f32
}
impl RenderedWindow {
pub fn new(surface: Surface, position: Point) -> RenderedWindow {
RenderedWindow {
surface,
start_position: position.clone(),
current_position: position.clone(),
previous_destination: position.clone(),
t: 2.0 // 2.0 is out of the 0.0 to 1.0 range and stops animation
}
}
pub fn update(
&mut self,
settings: &RendererSettings,
destination: Point,
dt: f32
) -> bool {
if destination != self.previous_destination {
self.t = 0.0;
self.start_position = self.current_position;
self.previous_destination = destination;
}
if (self.t - 1.0).abs() < std::f32::EPSILON {
return false;
}
if (self.t - 1.0).abs() < std::f32::EPSILON {
// We are at destination, move t out of 0-1 range to stop the animation
self.t = 2.0;
} else {
self.t = (self.t + dt / settings.animation_length).min(1.0);
}
self.current_position = ease_point(
ease_out_expo,
self.start_position,
destination,
self.t,
);
true
}
}
pub struct Renderer {
rendered_windows: HashMap<u64, RenderedWindow>,
paint: Paint,
shaper: CachingShaper,
cursor_renderer: CursorRenderer,
settings: RendererSettings,
pub paint: Paint,
pub shaper: CachingShaper,
pub default_style: Arc<Style>,
pub font_width: f32,
pub font_height: f32,
pub window_regions: Vec<(u64, Rect)>,
cursor_renderer: CursorRenderer,
}
impl Renderer {
pub fn new() -> Renderer {
let rendered_windows = HashMap::new();
let cursor_renderer = CursorRenderer::new();
let settings = SETTINGS.get::<RendererSettings>();
let mut paint = Paint::new(colors::WHITE, None);
paint.set_anti_alias(false);
let mut shaper = CachingShaper::new();
let (font_width, font_height) = shaper.font_base_dimensions();
let default_style = Arc::new(Style::new(Colors::new(
Some(colors::WHITE),
Some(colors::BLACK),
Some(colors::GREY),
)));
let window_regions = Vec::new();
let cursor_renderer = CursorRenderer::new();
Renderer {
rendered_windows,
cursor_renderer,
settings,
paint,
shaper,
default_style,
font_width,
font_height,
window_regions,
cursor_renderer,
}
}
@ -162,19 +115,17 @@ impl Renderer {
grid_pos: (u64, u64),
cell_width: u64,
style: &Option<Arc<Style>>,
default_style: &Arc<Style>,
floating: bool,
settings: &RendererSettings
) {
self.paint.set_blend_mode(BlendMode::Src);
let region = self.compute_text_region(grid_pos, cell_width);
let style = style.as_ref().unwrap_or(default_style);
let style = style.as_ref().unwrap_or(&self.default_style);
let mut color = style.background(&default_style.colors);
let mut color = style.background(&self.default_style.colors);
if floating {
color.a = color.a * settings.floating_opacity.min(1.0).max(0.0);
color.a = color.a * self.settings.floating_opacity.min(1.0).max(0.0);
}
self.paint.set_color(color.to_color());
@ -188,14 +139,13 @@ impl Renderer {
grid_pos: (u64, u64),
cell_width: u64,
style: &Option<Arc<Style>>,
default_style: &Arc<Style>,
) {
let (grid_x, grid_y) = grid_pos;
let x = grid_x as f32 * self.font_width;
let y = grid_y as f32 * self.font_height;
let width = cell_width as f32 * self.font_width;
let style = style.as_ref().unwrap_or(default_style);
let style = style.as_ref().unwrap_or(&self.default_style);
canvas.save();
@ -207,7 +157,7 @@ impl Renderer {
let line_position = self.shaper.underline_position();
let stroke_width = self.shaper.options.size / 10.0;
self.paint
.set_color(style.special(&default_style.colors).to_color());
.set_color(style.special(&self.default_style.colors).to_color());
self.paint.set_stroke_width(stroke_width);
if style.undercurl {
@ -227,7 +177,7 @@ impl Renderer {
}
self.paint
.set_color(style.foreground(&default_style.colors).to_color());
.set_color(style.foreground(&self.default_style.colors).to_color());
let text = text.trim_end();
if !text.is_empty() {
for blob in self
@ -242,203 +192,95 @@ impl Renderer {
if style.strikethrough {
let line_position = region.center_y();
self.paint
.set_color(style.special(&default_style.colors).to_color());
.set_color(style.special(&self.default_style.colors).to_color());
canvas.draw_line((x, line_position), (x + width, line_position), &self.paint);
}
canvas.restore();
}
pub fn build_window_surface(
&self,
gpu_canvas: &mut Canvas,
default_style: &Arc<Style>,
dimensions: (i32, i32),
) -> Surface {
let mut context = gpu_canvas.gpu_context().unwrap();
let budgeted = Budgeted::Yes;
let parent_image_info = gpu_canvas.image_info();
let image_info = ImageInfo::new(
dimensions,
parent_image_info.color_type(),
parent_image_info.alpha_type(),
parent_image_info.color_space(),
);
let surface_origin = SurfaceOrigin::TopLeft;
let mut surface = Surface::new_render_target(
&mut context,
budgeted,
&image_info,
None,
surface_origin,
None,
None,
)
.expect("Could not create surface");
let canvas = surface.canvas();
canvas.clear(default_style.colors.background.clone().unwrap().to_color());
surface
}
pub fn draw_window(
&mut self,
settings: &RendererSettings,
root_canvas: &mut Canvas,
window_render_info: WindowRenderInfo,
default_style: &Arc<Style>,
dt: f32
) -> (u64, Rect) {
let (grid_left, grid_top) = window_render_info.grid_position;
let window_destination = Point::new(grid_left as f32 * self.font_width, grid_top as f32 * self.font_height);
let image_width = (window_render_info.width as f32 * self.font_width) as i32;
let image_height = (window_render_info.height as f32 * self.font_height) as i32;
let mut rendered_window = self.rendered_windows
.remove(&window_render_info.grid_id)
.unwrap_or_else(|| {
let surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
RenderedWindow::new(surface, window_destination)
});
for command in window_render_info.draw_commands.into_iter() {
match command {
DrawCommand::Cell {
text, cell_width, grid_position, style
pub fn handle_draw_command(&mut self, root_canvas: &mut Canvas, draw_command: DrawCommand) {
match draw_command {
DrawCommand::Window {
grid_id,
command: WindowDrawCommand::Close
} => {
let mut canvas = rendered_window.surface.canvas();
self.draw_background(
&mut canvas,
grid_position,
cell_width,
&style,
&default_style,
window_render_info.floating,
settings
);
self.draw_foreground(
&mut canvas,
&text,
grid_position,
cell_width,
&style,
&default_style,
);
self.rendered_windows.remove(&grid_id);
},
DrawCommand::Scroll {
top, bot, left, right, rows, cols
DrawCommand::Window {
grid_id,
command
} => {
let scrolled_region = Rect::new(
left as f32 * self.font_width,
top as f32 * self.font_height,
right as f32 * self.font_width,
bot as f32 * self.font_height);
let snapshot = rendered_window.surface.image_snapshot();
let canvas = rendered_window.surface.canvas();
canvas.save();
canvas.clip_rect(scrolled_region, None, Some(false));
let mut translated_region = scrolled_region.clone();
translated_region.offset((-cols as f32 * self.font_width, -rows as f32 * self.font_height));
canvas.draw_image_rect(snapshot, Some((&scrolled_region, SrcRectConstraint::Fast)), translated_region, &self.paint);
canvas.restore();
if let Some(rendered_window) = self.rendered_windows.remove(&grid_id) {
let rendered_window = rendered_window.handle_window_draw_command(self, command);
self.rendered_windows.insert(grid_id, rendered_window);
} else if let WindowDrawCommand::Position {
grid_left, grid_top,
width, height, ..
} = command {
println!("Created window {}", grid_id);
let new_window = RenderedWindow::new(
root_canvas, &self, grid_id,
(grid_left as f32, grid_top as f32).into(),
width, height);
self.rendered_windows.insert(grid_id, new_window);
} else {
error!("WindowDrawCommand sent for uninitialized grid {}", grid_id);
}
},
DrawCommand::Resize => {
let mut old_surface = rendered_window.surface;
rendered_window.surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
old_surface.draw(rendered_window.surface.canvas(), (0.0, 0.0), None);
DrawCommand::UpdateCursor(new_cursor) => {
self.cursor_renderer.update_cursor(new_cursor);
},
DrawCommand::Clear => {
rendered_window.surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
}
}
}
if rendered_window.update(settings, window_destination, dt) {
REDRAW_SCHEDULER.queue_next_frame();
}
root_canvas.save();
let region = Rect::from_point_and_size(rendered_window.current_position, (image_width, image_height));
root_canvas.clip_rect(&region, None, Some(false));
if window_render_info.floating && settings.floating_blur {
let blur = blur((2.0, 2.0), None, None, None).unwrap();
let save_layer_rec = SaveLayerRec::default()
.backdrop(&blur)
.bounds(&region);
root_canvas.save_layer(&save_layer_rec);
DrawCommand::FontChanged(new_font) => {
if self.update_font(&new_font) {
// Resize all the grids
}
rendered_window.surface.draw(
root_canvas.as_mut(),
(rendered_window.current_position.x, rendered_window.current_position.y),
None);
if window_render_info.floating {
root_canvas.restore();
},
DrawCommand::DefaultStyleChanged(new_style) => {
self.default_style = Arc::new(new_style);
},
_ => { }
}
root_canvas.restore();
let window_position = rendered_window.current_position.clone();
self.rendered_windows.insert(window_render_info.grid_id, rendered_window);
(window_render_info.grid_id, Rect::from_point_and_size(window_position, (image_width as f32, image_height as f32)))
}
pub fn draw(
pub fn draw_frame(
&mut self,
gpu_canvas: &mut Canvas,
root_canvas: &mut Canvas,
coordinate_system_helper: &CoordinateSystemHelper,
dt: f32,
) -> bool {
) {
trace!("Rendering");
let settings = SETTINGS.get::<RendererSettings>();
let (render_info, default_style, cursor, guifont_setting) = {
let mut editor = EDITOR.lock();
(
editor.build_render_info(),
editor.default_style.clone(),
editor.cursor.clone(),
editor.guifont.clone(),
)
};
root_canvas.clear(self.default_style.colors.background.clone().unwrap().to_color());
gpu_canvas.clear(default_style.colors.background.clone().unwrap().to_color());
coordinate_system_helper.use_logical_coordinates(root_canvas);
let font_changed = guifont_setting
.map(|guifont| self.update_font(&guifont))
.unwrap_or(false);
let windows: Vec<&mut RenderedWindow> = {
let (mut root_windows, mut floating_windows): (Vec<&mut RenderedWindow>, Vec<&mut RenderedWindow>) = self.rendered_windows
.values_mut()
.filter(|window| !window.hidden)
.partition(|window| !window.floating);
for closed_window_id in render_info.closed_window_ids.iter() {
self.rendered_windows.remove(&closed_window_id);
}
root_windows.sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap());
floating_windows.sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap());
coordinate_system_helper.use_logical_coordinates(gpu_canvas);
root_windows.into_iter().chain(floating_windows.into_iter()).collect()
};
self.window_regions = render_info.windows
let settings = &self.settings;
let font_width = self.font_width;
let font_height = self.font_height;
self.window_regions = windows
.into_iter()
.map(|window_render_info| self.draw_window(&settings, gpu_canvas, window_render_info, &default_style, dt))
.map(|window| window.draw(root_canvas, settings, font_width, font_height, dt))
.collect();
self.cursor_renderer.draw(
cursor,
&default_style.colors,
&self.default_style.colors,
(self.font_width, self.font_height),
&mut self.shaper,
gpu_canvas,
root_canvas,
dt,
);
font_changed
}
}

@ -0,0 +1,234 @@
use skulpin::skia_safe::gpu::SurfaceOrigin;
use skulpin::skia_safe::{
Budgeted, Canvas, ImageInfo, Rect, Surface, Point, image_filters::blur
};
use skulpin::skia_safe::canvas::{
SaveLayerRec,
SrcRectConstraint
};
use super::{Renderer, RendererSettings};
use super::animation_utils::*;
use crate::editor::WindowDrawCommand;
use crate::redraw_scheduler::REDRAW_SCHEDULER;
fn build_window_surface(
parent_canvas: &mut Canvas,
renderer: &Renderer,
grid_width: u64,
grid_height: u64,
) -> Surface {
let dimensions = ((grid_width as f32 * renderer.font_width) as i32, (grid_height as f32 * renderer.font_height) as i32);
let mut context = parent_canvas.gpu_context().unwrap();
let budgeted = Budgeted::Yes;
let parent_image_info = parent_canvas.image_info();
let image_info = ImageInfo::new(
dimensions,
parent_image_info.color_type(),
parent_image_info.alpha_type(),
parent_image_info.color_space(),
);
let surface_origin = SurfaceOrigin::TopLeft;
let mut surface = Surface::new_render_target(
&mut context,
budgeted,
&image_info,
None,
surface_origin,
None,
None,
)
.expect("Could not create surface");
let canvas = surface.canvas();
canvas.clear(renderer.default_style.colors.background.clone().unwrap().to_color());
surface
}
pub struct RenderedWindow {
surface: Surface,
pub id: u64,
pub hidden: bool,
pub floating: bool,
grid_width: u64, grid_height: u64,
grid_start_position: Point,
grid_current_position: Point,
grid_destination: Point,
t: f32
}
impl RenderedWindow {
pub fn new(parent_canvas: &mut Canvas, renderer: &Renderer, id: u64, grid_position: Point, grid_width: u64, grid_height: u64) -> RenderedWindow {
let surface = build_window_surface(parent_canvas, renderer, grid_width, grid_height);
RenderedWindow {
surface,
id,
hidden: false,
floating: false,
grid_width,
grid_height,
grid_start_position: grid_position,
grid_current_position: grid_position,
grid_destination: grid_position,
t: 2.0 // 2.0 is out of the 0.0 to 1.0 range and stops animation
}
}
pub fn update(
&mut self,
settings: &RendererSettings,
dt: f32
) -> bool {
if (self.t - 1.0).abs() < std::f32::EPSILON {
return false;
}
if (self.t - 1.0).abs() < std::f32::EPSILON {
// We are at destination, move t out of 0-1 range to stop the animation
self.t = 2.0;
} else {
self.t = (self.t + dt / settings.animation_length).min(1.0);
}
self.grid_current_position = ease_point(
ease_out_expo,
self.grid_start_position,
self.grid_destination,
self.t,
);
true
}
pub fn draw(
&mut self,
root_canvas: &mut Canvas,
settings: &RendererSettings,
font_width: f32,
font_height: f32,
dt: f32
) -> (u64, Rect) {
let current_pixel_position = Point::new(self.grid_current_position.x * font_width, self.grid_current_position.y * font_height);
let image_width = (self.grid_width as f32 * font_width) as i32;
let image_height = (self.grid_height as f32 * font_height) as i32;
if self.update(settings, dt) {
REDRAW_SCHEDULER.queue_next_frame();
}
root_canvas.save();
let region = Rect::from_point_and_size(current_pixel_position, (image_width, image_height));
root_canvas.clip_rect(&region, None, Some(false));
if self.floating && settings.floating_blur {
let blur = blur((2.0, 2.0), None, None, None).unwrap();
let save_layer_rec = SaveLayerRec::default()
.backdrop(&blur)
.bounds(&region);
root_canvas.save_layer(&save_layer_rec);
}
self.surface.draw(
root_canvas.as_mut(),
(current_pixel_position.x, current_pixel_position.y),
None);
if self.floating {
root_canvas.restore();
}
root_canvas.restore();
let window_position = current_pixel_position.clone();
(self.id, Rect::from_point_and_size(window_position, (image_width as f32, image_height as f32)))
}
pub fn handle_window_draw_command(mut self, renderer: &mut Renderer, draw_command: WindowDrawCommand) -> Self {
match draw_command {
WindowDrawCommand::Position {
grid_left, grid_top,
width: grid_width,
height: grid_height,
floating
} => {
let new_destination: Point = (grid_left as f32, grid_top as f32).into();
if self.grid_destination != new_destination {
self.t = 0.0; // Reset animation as we have a new destination.
self.grid_start_position = self.grid_current_position;
self.grid_destination = new_destination;
}
if grid_width != self.grid_width || grid_height != self.grid_height {
let mut old_surface = self.surface;
self.surface = build_window_surface(old_surface.canvas(), &renderer, grid_width, grid_height);
old_surface.draw(self.surface.canvas(), (0.0, 0.0), None);
self.grid_width = grid_width;
self.grid_height = grid_height;
}
self.floating = floating;
},
WindowDrawCommand::Cell {
text, cell_width, window_left, window_top, style
} => {
let mut canvas = self.surface.canvas();
let grid_position = (window_left, window_top);
renderer.draw_background(
&mut canvas,
grid_position,
cell_width,
&style,
self.floating,
);
renderer.draw_foreground(
&mut canvas,
&text,
grid_position,
cell_width,
&style,
);
},
WindowDrawCommand::Scroll {
top, bot, left, right, rows, cols
} => {
let scrolled_region = Rect::new(
left as f32 * renderer.font_width,
top as f32 * renderer.font_height,
right as f32 * renderer.font_width,
bot as f32 * renderer.font_height);
let snapshot = self.surface.image_snapshot();
let canvas = self.surface.canvas();
canvas.save();
canvas.clip_rect(scrolled_region, None, Some(false));
let mut translated_region = scrolled_region.clone();
translated_region.offset((-cols as f32 * renderer.font_width, -rows as f32 * renderer.font_height));
canvas.draw_image_rect(snapshot, Some((&scrolled_region, SrcRectConstraint::Fast)), translated_region, &renderer.paint);
canvas.restore();
},
WindowDrawCommand::Clear => {
let canvas = self.surface.canvas();
self.surface = build_window_surface(canvas, &renderer, self.grid_width, self.grid_height);
},
WindowDrawCommand::Show => self.hidden = false,
WindowDrawCommand::Hide => self.hidden = true,
_ => {}
};
self
}
}

@ -0,0 +1,99 @@
fn compute_text_region(grid_pos: (u64, u64), cell_width: u64, font_width: f32, font_height: f32) -> Rect {
let (grid_x, grid_y) = grid_pos;
let x = grid_x as f32 * font_width;
let y = grid_y as f32 * font_height;
let width = cell_width as f32 * font_width as f32;
let height = font_height as f32;
Rect::new(x, y, x + width, y + height)
}
fn draw_background(
canvas: &mut Canvas,
grid_pos: (u64, u64),
cell_width: u64,
style: &Option<Arc<Style>>,
default_style: &Arc<Style>,
floating: bool,
settings: &RendererSettings
) {
self.paint.set_blend_mode(BlendMode::Src);
let region = self.compute_text_region(grid_pos, cell_width);
let style = style.as_ref().unwrap_or(default_style);
let mut color = style.background(&default_style.colors);
if floating {
color.a = color.a * settings.floating_opacity.min(1.0).max(0.0);
}
self.paint.set_color(color.to_color());
canvas.draw_rect(region, &self.paint);
}
fn draw_foreground(
canvas: &mut Canvas,
text: &str,
grid_pos: (u64, u64),
cell_width: u64,
style: &Option<Arc<Style>>,
default_style: &Arc<Style>,
) {
let (grid_x, grid_y) = grid_pos;
let x = grid_x as f32 * self.font_width;
let y = grid_y as f32 * self.font_height;
let width = cell_width as f32 * self.font_width;
let style = style.as_ref().unwrap_or(default_style);
canvas.save();
let region = self.compute_text_region(grid_pos, cell_width);
canvas.clip_rect(region, None, Some(false));
if style.underline || style.undercurl {
let line_position = self.shaper.underline_position();
let stroke_width = self.shaper.options.size / 10.0;
self.paint
.set_color(style.special(&default_style.colors).to_color());
self.paint.set_stroke_width(stroke_width);
if style.undercurl {
self.paint.set_path_effect(dash_path_effect::new(
&[stroke_width * 2.0, stroke_width * 2.0],
0.0,
));
} else {
self.paint.set_path_effect(None);
}
canvas.draw_line(
(x, y - line_position + self.font_height),
(x + width, y - line_position + self.font_height),
&self.paint,
);
}
self.paint
.set_color(style.foreground(&default_style.colors).to_color());
let text = text.trim_end();
if !text.is_empty() {
for blob in self
.shaper
.shape_cached(text, style.bold, style.italic)
.iter()
{
canvas.draw_text_blob(blob, (x, y), &self.paint);
}
}
if style.strikethrough {
let line_position = region.center_y();
self.paint
.set_color(style.special(&default_style.colors).to_color());
canvas.draw_line((x, line_position), (x + width, line_position), &self.paint);
}
canvas.restore();
}

@ -1,6 +1,8 @@
use std::sync::atomic::Ordering;
use std::thread::sleep;
use std::time::{Duration, Instant};
use std::sync::Arc;
use std::sync::atomic::{Ordering, AtomicBool};
use std::sync::mpsc::Receiver;
use log::{debug, error, info, trace};
use skulpin::sdl2;
@ -13,13 +15,15 @@ use skulpin::{
CoordinateSystem, LogicalSize, PhysicalSize, PresentMode, Renderer as SkulpinRenderer,
RendererBuilder, Sdl2Window, Window,
};
use tokio::sync::mpsc::UnboundedSender;
use crate::bridge::{produce_neovim_keybinding_string, UiCommand, BRIDGE};
use crate::editor::EDITOR;
use crate::editor::DrawCommand;
use crate::bridge::{produce_neovim_keybinding_string, UiCommand};
use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::renderer::Renderer;
use crate::settings::*;
use crate::INITIAL_DIMENSIONS;
use crate::error_handling::ResultPanicExplanation;
#[derive(RustEmbed)]
#[folder = "assets/"]
@ -34,15 +38,15 @@ fn windows_fix_dpi() {
}
}
fn handle_new_grid_size(new_size: LogicalSize, renderer: &Renderer) {
fn handle_new_grid_size(new_size: LogicalSize, renderer: &Renderer, ui_command_sender: &UnboundedSender<UiCommand>) {
if new_size.width > 0 && new_size.height > 0 {
let new_width = ((new_size.width + 1) as f32 / renderer.font_width) as u32;
let new_height = ((new_size.height + 1) as f32 / renderer.font_height) as u32;
// Add 1 here to make sure resizing doesn't change the grid size on startup
BRIDGE.queue_command(UiCommand::Resize {
ui_command_sender.send(UiCommand::Resize {
width: new_width,
height: new_height,
});
}).ok();
}
}
@ -53,6 +57,7 @@ struct WindowWrapper {
renderer: Renderer,
mouse_down: bool,
mouse_position: LogicalSize,
mouse_enabled: bool,
grid_id_under_mouse: u64,
title: String,
previous_size: LogicalSize,
@ -60,6 +65,8 @@ struct WindowWrapper {
fullscreen: bool,
cached_size: (u32, u32),
cached_position: (i32, i32),
ui_command_sender: UnboundedSender<UiCommand>,
running: Arc<AtomicBool>
}
pub fn window_geometry() -> Result<(u64, u64), String> {
@ -105,7 +112,7 @@ pub fn window_geometry_or_default() -> (u64, u64) {
}
impl WindowWrapper {
pub fn new() -> WindowWrapper {
pub fn new(ui_command_sender: UnboundedSender<UiCommand>, running: Arc<AtomicBool>) -> WindowWrapper {
let context = sdl2::init().expect("Failed to initialize sdl2");
let video_subsystem = context
.video()
@ -124,18 +131,6 @@ impl WindowWrapper {
windows_fix_dpi();
sdl2::hint::set("SDL_MOUSE_FOCUS_CLICKTHROUGH", "1");
// let icon = {
// let icon_data = Asset::get("nvim.ico").expect("Failed to read icon data");
// let icon = load_from_memory(&icon_data).expect("Failed to parse icon data");
// let (width, height) = icon.dimensions();
// let mut rgba = Vec::with_capacity((width * height) as usize * 4);
// for (_, _, pixel) in icon.pixels() {
// rgba.extend_from_slice(&pixel.to_rgba().0);
// }
// Icon::from_rgba(rgba, width, height).expect("Failed to create icon object")
// };
// info!("icon created");
let sdl_window = video_subsystem
.window("Neovide", logical_size.width, logical_size.height)
.position_centered()
@ -169,13 +164,16 @@ impl WindowWrapper {
width: 0,
height: 0,
},
mouse_enabled: true,
grid_id_under_mouse: 0,
title: String::from("Neovide"),
previous_size: logical_size,
transparency: 1.0,
fullscreen: false,
cached_size: (0, 0),
cached_position: (0, 0)
cached_position: (0, 0),
ui_command_sender,
running
}
}
@ -232,15 +230,6 @@ impl WindowWrapper {
}
pub fn synchronize_settings(&mut self) {
let editor_title = { EDITOR.lock().title.clone() };
if self.title != editor_title {
self.title = editor_title;
self.window
.set_title(&self.title)
.expect("Could not set title");
}
let transparency = { SETTINGS.get::<WindowSettings>().transparency };
if let Ok(opacity) = self.window.opacity() {
@ -257,8 +246,15 @@ impl WindowWrapper {
}
}
pub fn handle_title_changed(&mut self, new_title: String) {
self.title = new_title;
self.window
.set_title(&self.title)
.expect("Could not set title");
}
pub fn handle_quit(&mut self) {
BRIDGE.queue_command(UiCommand::Quit);
self.running.store(false, Ordering::Relaxed);
}
pub fn handle_keyboard_input(&mut self, keycode: Option<Keycode>, text: Option<String>) {
@ -275,7 +271,9 @@ impl WindowWrapper {
if let Some(keybinding_string) = produce_neovim_keybinding_string(keycode, text, modifiers)
{
BRIDGE.queue_command(UiCommand::Keyboard(keybinding_string));
self.ui_command_sender.send(UiCommand::Keyboard(keybinding_string)).unwrap_or_explained_panic(
"Could not send UI command from the window system to the neovim process.",
);
}
}
@ -306,38 +304,46 @@ impl WindowWrapper {
(grid_position.height as f32 / self.renderer.font_height) as u32
);
if self.mouse_down && previous_position != self.mouse_position {
if self.mouse_enabled && self.mouse_down && previous_position != self.mouse_position {
let (window_left, window_top) = top_window_position;
let adjusted_drag_left = self.mouse_position.width + (window_left / self.renderer.font_width) as u32;
let adjusted_drag_top = self.mouse_position.height + (window_top / self.renderer.font_height) as u32;
BRIDGE.queue_command(UiCommand::Drag {
self.ui_command_sender.send(UiCommand::Drag {
grid_id: self.grid_id_under_mouse,
position: (adjusted_drag_left, adjusted_drag_top),
});
}).ok();
}
}
}
pub fn handle_pointer_down(&mut self) {
BRIDGE.queue_command(UiCommand::MouseButton {
if self.mouse_enabled {
self.ui_command_sender.send(UiCommand::MouseButton {
action: String::from("press"),
grid_id: self.grid_id_under_mouse,
position: (self.mouse_position.width, self.mouse_position.height),
});
}).ok();
}
self.mouse_down = true;
}
pub fn handle_pointer_up(&mut self) {
BRIDGE.queue_command(UiCommand::MouseButton {
if self.mouse_enabled {
self.ui_command_sender.send(UiCommand::MouseButton {
action: String::from("release"),
grid_id: self.grid_id_under_mouse,
position: (self.mouse_position.width, self.mouse_position.height),
});
}).ok();
}
self.mouse_down = false;
}
pub fn handle_mouse_wheel(&mut self, x: i32, y: i32) {
if !self.mouse_enabled {
return;
}
let vertical_input_type = match y {
_ if y > 0 => Some("up"),
_ if y < 0 => Some("down"),
@ -345,11 +351,11 @@ impl WindowWrapper {
};
if let Some(input_type) = vertical_input_type {
BRIDGE.queue_command(UiCommand::Scroll {
self.ui_command_sender.send(UiCommand::Scroll {
direction: input_type.to_string(),
grid_id: self.grid_id_under_mouse,
position: (self.mouse_position.width, self.mouse_position.height),
});
}).ok();
}
let horizontal_input_type = match y {
@ -359,42 +365,42 @@ impl WindowWrapper {
};
if let Some(input_type) = horizontal_input_type {
BRIDGE.queue_command(UiCommand::Scroll {
self.ui_command_sender.send(UiCommand::Scroll {
direction: input_type.to_string(),
grid_id: self.grid_id_under_mouse,
position: (self.mouse_position.width, self.mouse_position.height),
});
}).ok();
}
}
pub fn handle_focus_lost(&mut self) {
BRIDGE.queue_command(UiCommand::FocusLost);
self.ui_command_sender.send(UiCommand::FocusLost).ok();
}
pub fn handle_focus_gained(&mut self) {
BRIDGE.queue_command(UiCommand::FocusGained);
self.ui_command_sender.send(UiCommand::FocusGained).ok();
REDRAW_SCHEDULER.queue_next_frame();
}
pub fn draw_frame(&mut self, dt: f32) -> VkResult<bool> {
pub fn draw_frame(&mut self, draw_commands: Vec<DrawCommand>, dt: f32) -> VkResult<bool> {
let sdl_window_wrapper = Sdl2Window::new(&self.window);
let new_size = sdl_window_wrapper.logical_size();
if self.previous_size != new_size {
handle_new_grid_size(new_size, &self.renderer);
handle_new_grid_size(new_size, &self.renderer, &self.ui_command_sender);
self.previous_size = new_size;
}
debug!("Render Triggered");
let current_size = self.previous_size;
if REDRAW_SCHEDULER.should_draw() || SETTINGS.get::<WindowSettings>().no_idle {
let renderer = &mut self.renderer;
self.skulpin_renderer
.draw(&sdl_window_wrapper, |canvas, coordinate_system_helper| {
if renderer.draw(canvas, &coordinate_system_helper, dt) {
handle_new_grid_size(current_size, &renderer)
for draw_command in draw_commands.into_iter() {
renderer.handle_draw_command(canvas, draw_command);
}
renderer.draw_frame(canvas, &coordinate_system_helper, dt)
})?;
Ok(true)
@ -430,8 +436,8 @@ pub fn initialize_settings() {
register_nvim_setting!("fullscreen", WindowSettings::fullscreen);
}
pub fn ui_loop() {
let mut window = WindowWrapper::new();
pub fn start_window(draw_command_receiver: Receiver<DrawCommand>, ui_command_sender: UnboundedSender<UiCommand>, running: Arc<AtomicBool>) {
let mut window = WindowWrapper::new(ui_command_sender.clone(), running.clone());
info!("Starting window event loop");
let mut event_pump = window
@ -442,7 +448,7 @@ pub fn ui_loop() {
let mut was_animating = false;
let mut previous_frame_start = Instant::now();
loop {
if !BRIDGE.running.load(Ordering::Relaxed) {
if !running.load(Ordering::Relaxed) {
break;
}
@ -458,7 +464,7 @@ pub fn ui_loop() {
match event {
Event::Quit { .. } => window.handle_quit(),
Event::DropFile { filename, .. } => {
BRIDGE.queue_command(UiCommand::FileDrop(filename));
ui_command_sender.send(UiCommand::FileDrop(filename)).ok();
}
Event::KeyDown {
keycode: received_keycode,
@ -498,7 +504,17 @@ pub fn ui_loop() {
1.0 / refresh_rate
};
match window.draw_frame(dt) {
let mut unhandled_draw_commands = Vec::new();
for draw_command in draw_command_receiver.try_iter() {
match draw_command {
DrawCommand::TitleChanged(new_title) => window.handle_title_changed(new_title),
DrawCommand::SetMouseEnabled(mouse_enabled) => window.mouse_enabled = mouse_enabled,
unhandled_command => unhandled_draw_commands.push(unhandled_command)
}
}
match window.draw_frame(unhandled_draw_commands, dt) {
Ok(animating) => {
was_animating = animating;
},

Loading…
Cancel
Save