mirror of https://github.com/sgoudham/neovide.git
updated readme
parent
5da81b4f26
commit
62d2ca407d
@ -1,146 +1,159 @@
|
||||
# Neovide [![Gitter](https://badges.gitter.im/neovide/community.svg)](https://gitter.im/neovide/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements, but it should act
|
||||
functionally like the terminal UI.
|
||||
|
||||
![Basic Screen Cap](./assets/BasicScreenCap.png)
|
||||
|
||||
I've been using this as my daily driver since November 2019. It should be relatively stable, but I'm still working out some kinks
|
||||
and ironing out some cross platform issues. In general it should be usable at this point, and if it isn't I consider that a bug and
|
||||
appreciate a report in the issues! Any help and ideas are also greatly appreciated.
|
||||
|
||||
I'm also very interested in suggestions code quality/style wise when it comes to Rust. I'm pretty new to the language and appreciate
|
||||
any critiques that you might have to offer. I won't take all of them, but I promise to consider anything you might have to offer.
|
||||
|
||||
## Features
|
||||
|
||||
Should be a standard full features Neovim GUI. Beyond that there are some visual niceties:
|
||||
|
||||
### Ligatures
|
||||
|
||||
Supports ligatures and full [HarfBuzz](https://www.freedesktop.org/wiki/Software/HarfBuzz/) backed font shaping.
|
||||
|
||||
![Ligatures](./assets/Ligatures.png)
|
||||
|
||||
### Animated Cursor
|
||||
|
||||
Cursor animates into position with a smear effect to improve tracking of cursor position.
|
||||
|
||||
![Animated Cursor](./assets/AnimatedCursor.gif)
|
||||
|
||||
### Emoji Support
|
||||
|
||||
Font fallback supports rendering of emoji not contained in the configured font.
|
||||
|
||||
![Emoji](./assets/Emoji.png)
|
||||
|
||||
### Some Nonsense ;)
|
||||
|
||||
```
|
||||
let g:neovide_cursor_vfx_mode = "railgun"
|
||||
```
|
||||
|
||||
![Railgun](./assets/Railgun.gif)
|
||||
|
||||
### More to Come
|
||||
|
||||
I've got more ideas for simple unobtrusive improvements. More to come.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is done almost completely via global neovide variables in your vim config and can be manipulated live at runtime. Details can be found [here](https://github.com/Kethku/neovide/wiki/Configuration).
|
||||
|
||||
## Install
|
||||
|
||||
Relatively recent binaries can be found in the [project releases](https://github.com/Kethku/neovide/releases). But if you want the latest and greatest you should clone it and build yourself.
|
||||
|
||||
Installing should be as simple as downloading the binary, making sure `nvim.exe` with version 0.4 or greater is on your path, and running it. Everything should be self contained.
|
||||
|
||||
## Building
|
||||
|
||||
Building instructions are somewhat limited at the moment. All the libraries I use are cross platform and should have
|
||||
support for Windows, Mac, and Linux. The rendering however is Vulkan-based, so driver support for Vulkan will be
|
||||
necessary. On Windows this should be enabled by default if you have a relatively recent system.
|
||||
|
||||
Note: Neovide requires neovim version 0.4 or greater.
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install the latest version of Rust. I recommend <https://rustup.rs/>
|
||||
2. Install CMake. I use chocolatey: `choco install cmake --installargs '"ADD_CMAKE_TO_PATH=System"' -y`
|
||||
3. Install LLVM. I use chocolatey: `choco install llvm -y`
|
||||
4. Ensure graphics libraries are up to date.
|
||||
5. `git clone https://github.com/Kethku/neovide`
|
||||
6. `cd neovide`
|
||||
7. `cargo build --release`
|
||||
8. Copy `./target/release/neovide.exe` to a known location and enjoy.
|
||||
|
||||
### Mac
|
||||
|
||||
1. Install the latest version of Rust. I recommend <https://rustup.rs/>
|
||||
2. Install CMake. Using homebrew: `brew install cmake`
|
||||
3. Install the Vulkan SDK. I'm told `brew cask install apenngrace/vulkan/vulkan-sdk` works, but I can't test locally to find out.
|
||||
4. `git clone https://github.com/Kethku/neovide`
|
||||
5. `cd neovide`
|
||||
6. `cargo build --release`
|
||||
7. Copy `./target/release/neovide` to a known location and enjoy.
|
||||
|
||||
Note: If you run into issues with the vulkan libraries being reported as not verified, this issue thread may help: https://github.com/Kethku/neovide/issues/167#issuecomment-593314579
|
||||
|
||||
### Linux
|
||||
|
||||
#### Arch Linux
|
||||
|
||||
There is an [AUR package for neovide](https://aur.archlinux.org/packages/neovide-git/).
|
||||
|
||||
```sh
|
||||
git clone https://aur.archlinux.org/neovide-git.git
|
||||
cd neovide
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Debian/Ubuntu
|
||||
|
||||
Note: Neovide has been successfully built on other destros but this reportedly works on ubuntu.
|
||||
|
||||
1. Install necessary dependencies
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y curl \
|
||||
gnupg ca-certificates git \
|
||||
gcc-multilib g++-multilib cmake libssl-dev pkg-config \
|
||||
libfreetype6-dev libasound2-dev libexpat1-dev libxcb-composite0-dev \
|
||||
libbz2-dev libsndio-dev freeglut3-dev libxmu-dev libxi-dev
|
||||
```
|
||||
|
||||
2. Install Vulkan SDK
|
||||
|
||||
```sh
|
||||
curl -sL "http://packages.lunarg.com/lunarg-signing-key-pub.asc" | sudo apt-key add -
|
||||
sudo curl -sLo "/etc/apt/sources.list.d/lunarg-vulkan-1.2.131-bionic.list" "http://packages.lunarg.com/vulkan/1.2.131/lunarg-vulkan-1.2.131-bionic.list"
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y vulkan-sdk
|
||||
```
|
||||
|
||||
Alternatively if you are running an amd graphics card you may have more success by installing amdvlk.
|
||||
https://github.com/Kethku/neovide/issues/209
|
||||
|
||||
3. Install Rust
|
||||
|
||||
`curl --proto '=https' --tlsv1.2 -sSf "https://sh.rustup.rs" | sh`
|
||||
|
||||
4. Clone the repository
|
||||
|
||||
`git clone "https://github.com/Kethku/neovide"`
|
||||
|
||||
5. Build
|
||||
|
||||
`cd neovide && ~/.cargo/bin/cargo build --release`
|
||||
|
||||
6. Copy `./target/release/neovide` to a known location and enjoy.
|
||||
|
||||
If you see an error complaining about DRI3 settings, links in this issue may help:
|
||||
<https://github.com/Kethku/neovide/issues/44#issuecomment-578618052>.
|
||||
|
||||
Note: If you run into libsndio errors, try building without default features which will disable static linking of the SDL
|
||||
library.
|
||||
# Neovide [![Gitter](https://badges.gitter.im/neovide/community.svg)](https://gitter.im/neovide/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements, but it should act
|
||||
functionally like the terminal UI.
|
||||
|
||||
![Basic Screen Cap](./assets/BasicScreenCap.png)
|
||||
|
||||
I've been using this as my daily driver since November 2019. It should be relatively stable, but I'm still working out some kinks
|
||||
and ironing out some cross platform issues. In general it should be usable at this point, and if it isn't I consider that a bug and
|
||||
appreciate a report in the issues! Any help and ideas are also greatly appreciated.
|
||||
|
||||
I'm also very interested in suggestions code quality/style wise when it comes to Rust. I'm pretty new to the language and appreciate
|
||||
any critiques that you might have to offer. I won't take all of them, but I promise to consider anything you might have to offer.
|
||||
|
||||
## Features
|
||||
|
||||
Should be a standard full features Neovim GUI. Beyond that there are some visual niceties:
|
||||
|
||||
### Ligatures
|
||||
|
||||
Supports ligatures and full [HarfBuzz](https://www.freedesktop.org/wiki/Software/HarfBuzz/) backed font shaping.
|
||||
|
||||
![Ligatures](./assets/Ligatures.png)
|
||||
|
||||
### Animated Cursor
|
||||
|
||||
Cursor animates into position with a smear effect to improve tracking of cursor position.
|
||||
|
||||
![Animated Cursor](./assets/AnimatedCursor.gif)
|
||||
|
||||
### Animated Windows
|
||||
|
||||
Windows animate into position when they are moved making it easier to see how layout changes happen.
|
||||
|
||||
![Animated Windows](./assets/AnimatedWindows.gif)
|
||||
|
||||
### Blurred Floating Windows
|
||||
|
||||
The backgrounds of floating windows are blurred improving the visual separation between foreground and background from
|
||||
built in window transparency.
|
||||
|
||||
![Blurred Floating Windows](./assets/BlurredFloatingWindows.png)
|
||||
|
||||
### Emoji Support
|
||||
|
||||
Font fallback supports rendering of emoji not contained in the configured font.
|
||||
|
||||
![Emoji](./assets/Emoji.png)
|
||||
|
||||
### Some Nonsense ;)
|
||||
|
||||
```
|
||||
let g:neovide_cursor_vfx_mode = "railgun"
|
||||
```
|
||||
|
||||
![Railgun](./assets/Railgun.gif)
|
||||
|
||||
### More to Come
|
||||
|
||||
I've got more ideas for simple unobtrusive improvements. More to come.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is done almost completely via global neovide variables in your vim config and can be manipulated live at runtime. Details can be found [here](https://github.com/Kethku/neovide/wiki/Configuration).
|
||||
|
||||
## Install
|
||||
|
||||
Relatively recent binaries can be found in the [project releases](https://github.com/Kethku/neovide/releases). But if you want the latest and greatest you should clone it and build yourself.
|
||||
|
||||
Installing should be as simple as downloading the binary, making sure `nvim.exe` with version 0.4 or greater is on your path, and running it. Everything should be self contained.
|
||||
|
||||
## Building
|
||||
|
||||
Building instructions are somewhat limited at the moment. All the libraries I use are cross platform and should have
|
||||
support for Windows, Mac, and Linux. The rendering however is Vulkan-based, so driver support for Vulkan will be
|
||||
necessary. On Windows this should be enabled by default if you have a relatively recent system.
|
||||
|
||||
Note: Neovide requires neovim version 0.4 or greater.
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install the latest version of Rust. I recommend <https://rustup.rs/>
|
||||
2. Install CMake. I use chocolatey: `choco install cmake --installargs '"ADD_CMAKE_TO_PATH=System"' -y`
|
||||
3. Install LLVM. I use chocolatey: `choco install llvm -y`
|
||||
4. Ensure graphics libraries are up to date.
|
||||
5. `git clone https://github.com/Kethku/neovide`
|
||||
6. `cd neovide`
|
||||
7. `cargo build --release`
|
||||
8. Copy `./target/release/neovide.exe` to a known location and enjoy.
|
||||
|
||||
### Mac
|
||||
|
||||
1. Install the latest version of Rust. I recommend <https://rustup.rs/>
|
||||
2. Install CMake. Using homebrew: `brew install cmake`
|
||||
3. Install the Vulkan SDK. I'm told `brew cask install apenngrace/vulkan/vulkan-sdk` works, but I can't test locally to find out.
|
||||
4. `git clone https://github.com/Kethku/neovide`
|
||||
5. `cd neovide`
|
||||
6. `cargo build --release`
|
||||
7. Copy `./target/release/neovide` to a known location and enjoy.
|
||||
|
||||
Note: If you run into issues with the vulkan libraries being reported as not verified, this issue thread may help: https://github.com/Kethku/neovide/issues/167#issuecomment-593314579
|
||||
|
||||
### Linux
|
||||
|
||||
#### Arch Linux
|
||||
|
||||
There is an [AUR package for neovide](https://aur.archlinux.org/packages/neovide-git/).
|
||||
|
||||
```sh
|
||||
git clone https://aur.archlinux.org/neovide-git.git
|
||||
cd neovide
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Debian/Ubuntu
|
||||
|
||||
Note: Neovide has been successfully built on other destros but this reportedly works on ubuntu.
|
||||
|
||||
1. Install necessary dependencies
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y curl \
|
||||
gnupg ca-certificates git \
|
||||
gcc-multilib g++-multilib cmake libssl-dev pkg-config \
|
||||
libfreetype6-dev libasound2-dev libexpat1-dev libxcb-composite0-dev \
|
||||
libbz2-dev libsndio-dev freeglut3-dev libxmu-dev libxi-dev
|
||||
```
|
||||
|
||||
2. Install Vulkan SDK
|
||||
|
||||
```sh
|
||||
curl -sL "http://packages.lunarg.com/lunarg-signing-key-pub.asc" | sudo apt-key add -
|
||||
sudo curl -sLo "/etc/apt/sources.list.d/lunarg-vulkan-1.2.131-bionic.list" "http://packages.lunarg.com/vulkan/1.2.131/lunarg-vulkan-1.2.131-bionic.list"
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y vulkan-sdk
|
||||
```
|
||||
|
||||
Alternatively if you are running an amd graphics card you may have more success by installing amdvlk.
|
||||
https://github.com/Kethku/neovide/issues/209
|
||||
|
||||
3. Install Rust
|
||||
|
||||
`curl --proto '=https' --tlsv1.2 -sSf "https://sh.rustup.rs" | sh`
|
||||
|
||||
4. Clone the repository
|
||||
|
||||
`git clone "https://github.com/Kethku/neovide"`
|
||||
|
||||
5. Build
|
||||
|
||||
`cd neovide && ~/.cargo/bin/cargo build --release`
|
||||
|
||||
6. Copy `./target/release/neovide` to a known location and enjoy.
|
||||
|
||||
If you see an error complaining about DRI3 settings, links in this issue may help:
|
||||
<https://github.com/Kethku/neovide/issues/44#issuecomment-578618052>.
|
||||
|
||||
Note: If you run into libsndio errors, try building without default features which will disable static linking of the SDL
|
||||
library.
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
After Width: | Height: | Size: 346 KiB |
@ -1,335 +1,335 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::{error, trace, warn};
|
||||
use skulpin::skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect};
|
||||
use skulpin::CoordinateSystemHelper;
|
||||
|
||||
pub mod animation_utils;
|
||||
mod caching_shaper;
|
||||
pub mod cursor_renderer;
|
||||
pub mod font_options;
|
||||
mod rendered_window;
|
||||
|
||||
pub use caching_shaper::CachingShaper;
|
||||
pub use font_options::*;
|
||||
pub use rendered_window::{RenderedWindow, WindowDrawDetails};
|
||||
|
||||
use crate::editor::{Colors, DrawCommand, Style, WindowDrawCommand};
|
||||
use crate::settings::*;
|
||||
use cursor_renderer::CursorRenderer;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RendererSettings {
|
||||
animation_length: f32,
|
||||
floating_opacity: f32,
|
||||
floating_blur: bool,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
SETTINGS.set(&RendererSettings {
|
||||
animation_length: 0.15,
|
||||
floating_opacity: 0.7,
|
||||
floating_blur: true,
|
||||
});
|
||||
|
||||
register_nvim_setting!(
|
||||
"window_animation_length",
|
||||
RendererSettings::animation_length
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"floating_window_opacity",
|
||||
RendererSettings::floating_opacity
|
||||
);
|
||||
register_nvim_setting!("floating_window_blur", RendererSettings::floating_blur);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct Renderer {
|
||||
rendered_windows: HashMap<u64, RenderedWindow>,
|
||||
cursor_renderer: CursorRenderer,
|
||||
|
||||
pub paint: Paint,
|
||||
pub shaper: CachingShaper,
|
||||
pub default_style: Arc<Style>,
|
||||
pub font_width: f32,
|
||||
pub font_height: f32,
|
||||
pub window_regions: Vec<WindowDrawDetails>,
|
||||
pub batched_draw_command_receiver: Receiver<Vec<DrawCommand>>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(batched_draw_command_receiver: Receiver<Vec<DrawCommand>>) -> Renderer {
|
||||
let rendered_windows = HashMap::new();
|
||||
let cursor_renderer = CursorRenderer::new();
|
||||
|
||||
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();
|
||||
|
||||
Renderer {
|
||||
rendered_windows,
|
||||
cursor_renderer,
|
||||
|
||||
paint,
|
||||
shaper,
|
||||
default_style,
|
||||
font_width,
|
||||
font_height,
|
||||
window_regions,
|
||||
batched_draw_command_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_font(&mut self, guifont_setting: &str) -> bool {
|
||||
let updated = self.shaper.update_font(guifont_setting);
|
||||
if updated {
|
||||
let (font_width, font_height) = self.shaper.font_base_dimensions();
|
||||
self.font_width = font_width;
|
||||
self.font_height = font_height.ceil();
|
||||
}
|
||||
updated
|
||||
}
|
||||
|
||||
fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect {
|
||||
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 as f32;
|
||||
let height = self.font_height as f32;
|
||||
Rect::new(x, y, x + width, y + height)
|
||||
}
|
||||
|
||||
fn draw_background(
|
||||
&mut self,
|
||||
canvas: &mut Canvas,
|
||||
grid_pos: (u64, u64),
|
||||
cell_width: u64,
|
||||
style: &Option<Arc<Style>>,
|
||||
) {
|
||||
self.paint.set_blend_mode(BlendMode::Src);
|
||||
|
||||
let region = self.compute_text_region(grid_pos, cell_width);
|
||||
let style = style.as_ref().unwrap_or(&self.default_style);
|
||||
|
||||
self.paint
|
||||
.set_color(style.background(&self.default_style.colors).to_color());
|
||||
canvas.draw_rect(region, &self.paint);
|
||||
}
|
||||
|
||||
fn draw_foreground(
|
||||
&mut self,
|
||||
canvas: &mut Canvas,
|
||||
text: &str,
|
||||
grid_pos: (u64, u64),
|
||||
cell_width: u64,
|
||||
style: &Option<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(&self.default_style);
|
||||
|
||||
canvas.save();
|
||||
|
||||
let region = self.compute_text_region(grid_pos, cell_width);
|
||||
|
||||
canvas.clip_rect(region, None, Some(false));
|
||||
|
||||
self.paint.set_blend_mode(BlendMode::Src);
|
||||
let transparent = Color::from_argb(0, 0, 0, 0);
|
||||
self.paint.set_color(transparent);
|
||||
canvas.draw_rect(region, &self.paint);
|
||||
|
||||
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(&self.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(&self.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(&self.default_style.colors).to_color());
|
||||
canvas.draw_line((x, line_position), (x + width, line_position), &self.paint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
pub fn handle_draw_command(&mut self, root_canvas: &mut Canvas, draw_command: DrawCommand) {
|
||||
warn!("{:?}", &draw_command);
|
||||
match draw_command {
|
||||
DrawCommand::Window {
|
||||
grid_id,
|
||||
command: WindowDrawCommand::Close,
|
||||
} => {
|
||||
self.rendered_windows.remove(&grid_id);
|
||||
}
|
||||
DrawCommand::Window { grid_id, command } => {
|
||||
if let Some(rendered_window) = self.rendered_windows.remove(&grid_id) {
|
||||
warn!("Window positioned {}", 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
|
||||
{
|
||||
warn!("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::UpdateCursor(new_cursor) => {
|
||||
self.cursor_renderer.update_cursor(new_cursor);
|
||||
}
|
||||
DrawCommand::FontChanged(new_font) => {
|
||||
if self.update_font(&new_font) {
|
||||
// Resize all the grids
|
||||
}
|
||||
}
|
||||
DrawCommand::DefaultStyleChanged(new_style) => {
|
||||
self.default_style = Arc::new(new_style);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_frame(
|
||||
&mut self,
|
||||
root_canvas: &mut Canvas,
|
||||
coordinate_system_helper: &CoordinateSystemHelper,
|
||||
dt: f32,
|
||||
) -> bool {
|
||||
trace!("Rendering");
|
||||
let mut font_changed = false;
|
||||
|
||||
let draw_commands: Vec<DrawCommand> = self
|
||||
.batched_draw_command_receiver
|
||||
.try_iter() // Iterator of Vec of DrawCommand
|
||||
.map(|batch| batch.into_iter()) // Iterator of Iterator of DrawCommand
|
||||
.flatten() // Iterator of DrawCommand
|
||||
.collect(); // Vec of DrawCommand
|
||||
for draw_command in draw_commands.into_iter() {
|
||||
if let DrawCommand::FontChanged(_) = draw_command {
|
||||
font_changed = true;
|
||||
}
|
||||
self.handle_draw_command(root_canvas, draw_command);
|
||||
}
|
||||
|
||||
root_canvas.clear(
|
||||
self.default_style
|
||||
.colors
|
||||
.background
|
||||
.clone()
|
||||
.unwrap()
|
||||
.to_color(),
|
||||
);
|
||||
|
||||
root_canvas.save();
|
||||
|
||||
if let Some(root_window) = self.rendered_windows.get(&1) {
|
||||
let clip_rect = root_window.pixel_region(self.font_width, self.font_height);
|
||||
root_canvas.clip_rect(&clip_rect, None, Some(false));
|
||||
}
|
||||
|
||||
coordinate_system_helper.use_logical_coordinates(root_canvas);
|
||||
|
||||
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);
|
||||
|
||||
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());
|
||||
|
||||
root_windows
|
||||
.into_iter()
|
||||
.chain(floating_windows.into_iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let settings = SETTINGS.get::<RendererSettings>();
|
||||
let font_width = self.font_width;
|
||||
let font_height = self.font_height;
|
||||
self.window_regions = windows
|
||||
.into_iter()
|
||||
.map(|window| window.draw(root_canvas, &settings, font_width, font_height, dt))
|
||||
.collect();
|
||||
|
||||
self.cursor_renderer.draw(
|
||||
&self.default_style.colors,
|
||||
(self.font_width, self.font_height),
|
||||
&mut self.shaper,
|
||||
root_canvas,
|
||||
dt,
|
||||
);
|
||||
|
||||
root_canvas.restore();
|
||||
|
||||
font_changed
|
||||
}
|
||||
}
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::{error, trace, warn};
|
||||
use skulpin::skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect};
|
||||
use skulpin::CoordinateSystemHelper;
|
||||
|
||||
pub mod animation_utils;
|
||||
mod caching_shaper;
|
||||
pub mod cursor_renderer;
|
||||
pub mod font_options;
|
||||
mod rendered_window;
|
||||
|
||||
pub use caching_shaper::CachingShaper;
|
||||
pub use font_options::*;
|
||||
pub use rendered_window::{RenderedWindow, WindowDrawDetails};
|
||||
|
||||
use crate::editor::{Colors, DrawCommand, Style, WindowDrawCommand};
|
||||
use crate::settings::*;
|
||||
use cursor_renderer::CursorRenderer;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RendererSettings {
|
||||
animation_length: f32,
|
||||
floating_opacity: f32,
|
||||
floating_blur: bool,
|
||||
}
|
||||
|
||||
pub fn initialize_settings() {
|
||||
SETTINGS.set(&RendererSettings {
|
||||
animation_length: 0.15,
|
||||
floating_opacity: 0.7,
|
||||
floating_blur: true,
|
||||
});
|
||||
|
||||
register_nvim_setting!(
|
||||
"window_animation_length",
|
||||
RendererSettings::animation_length
|
||||
);
|
||||
register_nvim_setting!(
|
||||
"floating_window_opacity",
|
||||
RendererSettings::floating_opacity
|
||||
);
|
||||
register_nvim_setting!("floating_window_blur", RendererSettings::floating_blur);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct Renderer {
|
||||
rendered_windows: HashMap<u64, RenderedWindow>,
|
||||
cursor_renderer: CursorRenderer,
|
||||
|
||||
pub paint: Paint,
|
||||
pub shaper: CachingShaper,
|
||||
pub default_style: Arc<Style>,
|
||||
pub font_width: f32,
|
||||
pub font_height: f32,
|
||||
pub window_regions: Vec<WindowDrawDetails>,
|
||||
pub batched_draw_command_receiver: Receiver<Vec<DrawCommand>>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(batched_draw_command_receiver: Receiver<Vec<DrawCommand>>) -> Renderer {
|
||||
let rendered_windows = HashMap::new();
|
||||
let cursor_renderer = CursorRenderer::new();
|
||||
|
||||
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();
|
||||
|
||||
Renderer {
|
||||
rendered_windows,
|
||||
cursor_renderer,
|
||||
|
||||
paint,
|
||||
shaper,
|
||||
default_style,
|
||||
font_width,
|
||||
font_height,
|
||||
window_regions,
|
||||
batched_draw_command_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_font(&mut self, guifont_setting: &str) -> bool {
|
||||
let updated = self.shaper.update_font(guifont_setting);
|
||||
if updated {
|
||||
let (font_width, font_height) = self.shaper.font_base_dimensions();
|
||||
self.font_width = font_width;
|
||||
self.font_height = font_height.ceil();
|
||||
}
|
||||
updated
|
||||
}
|
||||
|
||||
fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect {
|
||||
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 as f32;
|
||||
let height = self.font_height as f32;
|
||||
Rect::new(x, y, x + width, y + height)
|
||||
}
|
||||
|
||||
fn draw_background(
|
||||
&mut self,
|
||||
canvas: &mut Canvas,
|
||||
grid_pos: (u64, u64),
|
||||
cell_width: u64,
|
||||
style: &Option<Arc<Style>>,
|
||||
) {
|
||||
self.paint.set_blend_mode(BlendMode::Src);
|
||||
|
||||
let region = self.compute_text_region(grid_pos, cell_width);
|
||||
let style = style.as_ref().unwrap_or(&self.default_style);
|
||||
|
||||
self.paint
|
||||
.set_color(style.background(&self.default_style.colors).to_color());
|
||||
canvas.draw_rect(region, &self.paint);
|
||||
}
|
||||
|
||||
fn draw_foreground(
|
||||
&mut self,
|
||||
canvas: &mut Canvas,
|
||||
text: &str,
|
||||
grid_pos: (u64, u64),
|
||||
cell_width: u64,
|
||||
style: &Option<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(&self.default_style);
|
||||
|
||||
canvas.save();
|
||||
|
||||
let region = self.compute_text_region(grid_pos, cell_width);
|
||||
|
||||
canvas.clip_rect(region, None, Some(false));
|
||||
|
||||
self.paint.set_blend_mode(BlendMode::Src);
|
||||
let transparent = Color::from_argb(0, 0, 0, 0);
|
||||
self.paint.set_color(transparent);
|
||||
canvas.draw_rect(region, &self.paint);
|
||||
|
||||
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(&self.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(&self.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(&self.default_style.colors).to_color());
|
||||
canvas.draw_line((x, line_position), (x + width, line_position), &self.paint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
pub fn handle_draw_command(&mut self, root_canvas: &mut Canvas, draw_command: DrawCommand) {
|
||||
warn!("{:?}", &draw_command);
|
||||
match draw_command {
|
||||
DrawCommand::Window {
|
||||
grid_id,
|
||||
command: WindowDrawCommand::Close,
|
||||
} => {
|
||||
self.rendered_windows.remove(&grid_id);
|
||||
}
|
||||
DrawCommand::Window { grid_id, command } => {
|
||||
if let Some(rendered_window) = self.rendered_windows.remove(&grid_id) {
|
||||
warn!("Window positioned {}", 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
|
||||
{
|
||||
warn!("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::UpdateCursor(new_cursor) => {
|
||||
self.cursor_renderer.update_cursor(new_cursor);
|
||||
}
|
||||
DrawCommand::FontChanged(new_font) => {
|
||||
if self.update_font(&new_font) {
|
||||
// Resize all the grids
|
||||
}
|
||||
}
|
||||
DrawCommand::DefaultStyleChanged(new_style) => {
|
||||
self.default_style = Arc::new(new_style);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_frame(
|
||||
&mut self,
|
||||
root_canvas: &mut Canvas,
|
||||
coordinate_system_helper: &CoordinateSystemHelper,
|
||||
dt: f32,
|
||||
) -> bool {
|
||||
trace!("Rendering");
|
||||
let mut font_changed = false;
|
||||
|
||||
let draw_commands: Vec<DrawCommand> = self
|
||||
.batched_draw_command_receiver
|
||||
.try_iter() // Iterator of Vec of DrawCommand
|
||||
.map(|batch| batch.into_iter()) // Iterator of Iterator of DrawCommand
|
||||
.flatten() // Iterator of DrawCommand
|
||||
.collect(); // Vec of DrawCommand
|
||||
for draw_command in draw_commands.into_iter() {
|
||||
if let DrawCommand::FontChanged(_) = draw_command {
|
||||
font_changed = true;
|
||||
}
|
||||
self.handle_draw_command(root_canvas, draw_command);
|
||||
}
|
||||
|
||||
root_canvas.clear(
|
||||
self.default_style
|
||||
.colors
|
||||
.background
|
||||
.clone()
|
||||
.unwrap()
|
||||
.to_color(),
|
||||
);
|
||||
|
||||
root_canvas.save();
|
||||
|
||||
if let Some(root_window) = self.rendered_windows.get(&1) {
|
||||
let clip_rect = root_window.pixel_region(self.font_width, self.font_height);
|
||||
root_canvas.clip_rect(&clip_rect, None, Some(false));
|
||||
}
|
||||
|
||||
coordinate_system_helper.use_logical_coordinates(root_canvas);
|
||||
|
||||
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);
|
||||
|
||||
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());
|
||||
|
||||
root_windows
|
||||
.into_iter()
|
||||
.chain(floating_windows.into_iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let settings = SETTINGS.get::<RendererSettings>();
|
||||
let font_width = self.font_width;
|
||||
let font_height = self.font_height;
|
||||
self.window_regions = windows
|
||||
.into_iter()
|
||||
.map(|window| window.draw(root_canvas, &settings, font_width, font_height, dt))
|
||||
.collect();
|
||||
|
||||
self.cursor_renderer.draw(
|
||||
&self.default_style.colors,
|
||||
(self.font_width, self.font_height),
|
||||
&mut self.shaper,
|
||||
root_canvas,
|
||||
dt,
|
||||
);
|
||||
|
||||
root_canvas.restore();
|
||||
|
||||
font_changed
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue