mirror of
https://github.com/neovide/neovide.git
synced 2026-09-10 07:16:25 -04:00
feat: render same z-index together (#2467)
* feat: render same z-index together * make clippy happy * uniform blends in layer, render all background first, then foreground * fix background blends * add test for telescope cases * fix telescope case * fix test case * Remove custom hull algorithm in favor of skia's built in one * Remove zindex step setting and use consecutive groupings of floating windows instead * Swap to iter_lines * Add back in the floating order which was removed due to a clippy warning but is now needed again * Address clippy issues and refactor rendered window lines functions to split out their components into parts * Fix overzealous clipping --------- Co-authored-by: Hawtian Wang <twistoy.wang@gmail.com> Co-authored-by: Hawtian Wang <hawtian.wang@twistoy.cn>
This commit is contained in:
parent
deb4dd5278
commit
278bf6bfe5
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -6,3 +6,8 @@
|
|||
.DS_Store
|
||||
website/book
|
||||
*.AppImage
|
||||
.envrc
|
||||
flake.nix
|
||||
flake.lock
|
||||
.direnv/
|
||||
.devenv/
|
||||
|
|
|
|||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -1147,6 +1147,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
|
|
@ -1447,6 +1453,7 @@ dependencies = [
|
|||
"glutin-winit",
|
||||
"icrate",
|
||||
"image",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ gl = "0.14.0"
|
|||
glutin = "0.31.1"
|
||||
glutin-winit = "0.4.2"
|
||||
image = { version = "0.25.0", default-features = false, features = ["ico"] }
|
||||
indoc = "2.0.5"
|
||||
itertools = "0.12.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.16"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ pub mod fonts;
|
|||
pub mod grid_renderer;
|
||||
pub mod opengl;
|
||||
pub mod profiler;
|
||||
mod rendered_layer;
|
||||
mod rendered_window;
|
||||
mod vsync;
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use skia_safe::Canvas;
|
||||
use winit::{
|
||||
|
|
@ -28,6 +30,7 @@ use crate::{
|
|||
bridge::EditorMode,
|
||||
editor::{Cursor, Style},
|
||||
profiling::{tracy_create_gpu_context, tracy_named_frame, tracy_zone},
|
||||
renderer::rendered_layer::{group_windows, FloatingLayer},
|
||||
settings::*,
|
||||
units::{to_skia_rect, GridPos, GridRect, GridSize, PixelPos},
|
||||
window::{ShouldRender, UserEvent},
|
||||
|
|
@ -180,7 +183,7 @@ impl Renderer {
|
|||
root_canvas.clip_rect(clip_rect, None, Some(false));
|
||||
}
|
||||
|
||||
let windows: Vec<&mut RenderedWindow> = {
|
||||
let (root_windows, floating_layers) = {
|
||||
let (mut root_windows, mut floating_windows): (
|
||||
Vec<&mut RenderedWindow>,
|
||||
Vec<&mut RenderedWindow>,
|
||||
|
|
@ -192,16 +195,61 @@ impl Renderer {
|
|||
|
||||
root_windows
|
||||
.sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap());
|
||||
|
||||
floating_windows.sort_by(floating_sort);
|
||||
|
||||
root_windows.into_iter().chain(floating_windows).collect()
|
||||
let mut floating_layers = vec![];
|
||||
|
||||
let mut base_zindex = 0;
|
||||
let mut last_zindex = 0;
|
||||
let mut current_windows = vec![];
|
||||
|
||||
for window in floating_windows {
|
||||
let zindex = window.anchor_info.as_ref().unwrap().sort_order;
|
||||
log::debug!("zindex: {}, base: {}", zindex, base_zindex);
|
||||
// Group floating windows by consecutive z indices
|
||||
if zindex - last_zindex > 1 && !current_windows.is_empty() {
|
||||
for windows in group_windows(current_windows, grid_scale) {
|
||||
floating_layers.push(FloatingLayer {
|
||||
sort_order: base_zindex,
|
||||
windows,
|
||||
});
|
||||
}
|
||||
current_windows = vec![];
|
||||
}
|
||||
|
||||
if current_windows.is_empty() {
|
||||
base_zindex = zindex;
|
||||
}
|
||||
current_windows.push(window);
|
||||
last_zindex = zindex;
|
||||
}
|
||||
|
||||
if !current_windows.is_empty() {
|
||||
for windows in group_windows(current_windows, grid_scale) {
|
||||
floating_layers.push(FloatingLayer {
|
||||
sort_order: base_zindex,
|
||||
windows,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for layer in &mut floating_layers {
|
||||
layer.windows.sort_by(floating_sort);
|
||||
log::debug!(
|
||||
"layer: {:?}",
|
||||
layer
|
||||
.windows
|
||||
.iter()
|
||||
.map(|w| (w.id, w.anchor_info.as_ref().unwrap().sort_order))
|
||||
.collect_vec()
|
||||
);
|
||||
}
|
||||
|
||||
(root_windows, floating_layers)
|
||||
};
|
||||
|
||||
let settings = SETTINGS.get::<RendererSettings>();
|
||||
let mut floating_rects = Vec::new();
|
||||
|
||||
self.window_regions = windows
|
||||
let root_window_regions = root_windows
|
||||
.into_iter()
|
||||
.map(|window| {
|
||||
window.draw(
|
||||
|
|
@ -209,11 +257,26 @@ impl Renderer {
|
|||
&settings,
|
||||
default_background.with_a((255.0 * transparency) as u8),
|
||||
grid_scale,
|
||||
&mut floating_rects,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
.collect_vec();
|
||||
|
||||
let floating_window_regions = floating_layers
|
||||
.into_iter()
|
||||
.flat_map(|mut layer| {
|
||||
layer.draw(
|
||||
root_canvas,
|
||||
&settings,
|
||||
default_background.with_a((255.0 * transparency) as u8),
|
||||
grid_scale,
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
self.window_regions = root_window_regions
|
||||
.into_iter()
|
||||
.chain(floating_window_regions)
|
||||
.collect();
|
||||
self.cursor_renderer
|
||||
.draw(&mut self.grid_renderer, root_canvas);
|
||||
|
||||
|
|
|
|||
224
src/renderer/rendered_layer.rs
Normal file
224
src/renderer/rendered_layer.rs
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
use itertools::Itertools;
|
||||
use skia_safe::{
|
||||
canvas::SaveLayerRec,
|
||||
image_filters::blur,
|
||||
utils::shadow_utils::{draw_shadow, ShadowFlags},
|
||||
BlendMode, Canvas, ClipOp, Color, Paint, Path, PathOp, Point3, Rect,
|
||||
};
|
||||
|
||||
use crate::units::{to_skia_rect, GridScale, PixelRect};
|
||||
|
||||
use super::{RenderedWindow, RendererSettings, WindowDrawDetails};
|
||||
|
||||
struct LayerWindow<'w> {
|
||||
window: &'w mut RenderedWindow,
|
||||
group: usize,
|
||||
}
|
||||
|
||||
pub struct FloatingLayer<'w> {
|
||||
pub sort_order: u64,
|
||||
pub windows: Vec<&'w mut RenderedWindow>,
|
||||
}
|
||||
|
||||
impl<'w> FloatingLayer<'w> {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
root_canvas: &Canvas,
|
||||
settings: &RendererSettings,
|
||||
default_background: Color,
|
||||
grid_scale: GridScale,
|
||||
) -> Vec<WindowDrawDetails> {
|
||||
let pixel_regions = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|window| window.pixel_region(grid_scale))
|
||||
.collect::<Vec<_>>();
|
||||
let (silhouette, bound_rect) = build_silhouette(&pixel_regions);
|
||||
let has_transparency = default_background.a() != 255
|
||||
|| self.windows.iter().any(|window| window.has_transparency());
|
||||
|
||||
self._draw_shadow(root_canvas, &silhouette, settings);
|
||||
|
||||
root_canvas.save();
|
||||
root_canvas.clip_path(&silhouette, None, Some(false));
|
||||
let need_blur = has_transparency || settings.floating_blur;
|
||||
|
||||
if need_blur {
|
||||
if let Some(blur) = blur(
|
||||
(
|
||||
settings.floating_blur_amount_x,
|
||||
settings.floating_blur_amount_y,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
let paint = Paint::default()
|
||||
.set_anti_alias(false)
|
||||
.set_blend_mode(BlendMode::Src)
|
||||
.to_owned();
|
||||
let save_layer_rec = SaveLayerRec::default()
|
||||
.backdrop(&blur)
|
||||
.bounds(&bound_rect)
|
||||
.paint(&paint);
|
||||
root_canvas.save_layer(&save_layer_rec);
|
||||
root_canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
let paint = Paint::default()
|
||||
.set_anti_alias(false)
|
||||
.set_color(Color::from_argb(255, 255, 255, default_background.a()))
|
||||
.set_blend_mode(BlendMode::SrcOver)
|
||||
.to_owned();
|
||||
|
||||
let save_layer_rec = SaveLayerRec::default().bounds(&bound_rect).paint(&paint);
|
||||
|
||||
root_canvas.save_layer(&save_layer_rec);
|
||||
root_canvas.clear(default_background.with_a(255));
|
||||
|
||||
let regions = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|window| window.pixel_region(grid_scale))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let blend = self.uniform_background_blend();
|
||||
|
||||
self.windows.iter_mut().for_each(|window| {
|
||||
window.update_blend(blend);
|
||||
});
|
||||
|
||||
let mut ret = vec![];
|
||||
|
||||
(0..self.windows.len()).for_each(|i| {
|
||||
let window = &mut self.windows[i];
|
||||
window.draw_background_surface(root_canvas, regions[i], grid_scale);
|
||||
});
|
||||
(0..self.windows.len()).for_each(|i| {
|
||||
let window = &mut self.windows[i];
|
||||
window.draw_foreground_surface(root_canvas, regions[i], grid_scale);
|
||||
|
||||
ret.push(WindowDrawDetails {
|
||||
id: window.id,
|
||||
region: regions[i],
|
||||
floating_order: window.anchor_info.as_ref().map(|v| v.sort_order),
|
||||
});
|
||||
});
|
||||
|
||||
root_canvas.restore();
|
||||
|
||||
root_canvas.restore();
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn uniform_background_blend(&self) -> u8 {
|
||||
self.windows
|
||||
.iter()
|
||||
.filter_map(|window| window.get_smallest_blend_value())
|
||||
.min()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn _draw_shadow(&self, root_canvas: &Canvas, path: &Path, settings: &RendererSettings) {
|
||||
root_canvas.save();
|
||||
// We clip using the Difference op to make sure that the shadow isn't rendered inside
|
||||
// the window itself.
|
||||
root_canvas.clip_path(path, Some(ClipOp::Difference), None);
|
||||
// The light angle is specified in degrees from the vertical, so we first convert them
|
||||
// to radians and then use sin/cos to get the y and z components of the light
|
||||
let light_angle_radians = settings.light_angle_degrees.to_radians();
|
||||
draw_shadow(
|
||||
root_canvas,
|
||||
path,
|
||||
// Specifies how far from the root canvas the shadow casting rect is. We just use
|
||||
// the z component here to set it a constant distance away.
|
||||
Point3::new(0., 0., settings.floating_z_height),
|
||||
// Because we use the DIRECTIONAL_LIGHT shadow flag, this specifies the angle that
|
||||
// the light is coming from.
|
||||
Point3::new(0., -light_angle_radians.sin(), light_angle_radians.cos()),
|
||||
// This is roughly equal to the apparent radius of the light .
|
||||
5.,
|
||||
Color::from_argb((0.03 * 255.) as u8, 0, 0, 0),
|
||||
Color::from_argb((0.35 * 255.) as u8, 0, 0, 0),
|
||||
// Directional Light flag is necessary to make the shadow render consistently
|
||||
// across various sizes of floating windows. It effects how the light direction is
|
||||
// processed.
|
||||
Some(ShadowFlags::DIRECTIONAL_LIGHT),
|
||||
);
|
||||
root_canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_window_group(windows: &mut Vec<LayerWindow>, index: usize) -> usize {
|
||||
if windows[index].group != index {
|
||||
windows[index].group = get_window_group(windows, windows[index].group);
|
||||
}
|
||||
windows[index].group
|
||||
}
|
||||
|
||||
fn rect_intersect(a: &Rect, b: &Rect) -> bool {
|
||||
Rect::intersects2(a, b)
|
||||
}
|
||||
|
||||
fn group_windows_with_regions(windows: &mut Vec<LayerWindow>, regions: &[PixelRect<f32>]) {
|
||||
for i in 0..windows.len() {
|
||||
for j in i + 1..windows.len() {
|
||||
let group_i = get_window_group(windows, i);
|
||||
let group_j = get_window_group(windows, j);
|
||||
if group_i != group_j
|
||||
&& rect_intersect(&to_skia_rect(®ions[i]), &to_skia_rect(®ions[j]))
|
||||
{
|
||||
let new_group = group_i.min(group_j);
|
||||
if group_i != group_j {
|
||||
windows[group_i].group = new_group;
|
||||
windows[group_j].group = new_group;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_windows(
|
||||
windows: Vec<&mut RenderedWindow>,
|
||||
grid_scale: GridScale,
|
||||
) -> Vec<Vec<&mut RenderedWindow>> {
|
||||
let mut windows = windows
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, window)| LayerWindow {
|
||||
window,
|
||||
group: index,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let regions = windows
|
||||
.iter()
|
||||
.map(|window| window.window.pixel_region(grid_scale))
|
||||
.collect::<Vec<_>>();
|
||||
group_windows_with_regions(&mut windows, ®ions);
|
||||
for i in 0..windows.len() {
|
||||
let _ = get_window_group(&mut windows, i);
|
||||
}
|
||||
windows
|
||||
.into_iter()
|
||||
.group_by(|window| window.group)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v.map(|w| w.window).collect::<Vec<_>>())
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
fn build_silhouette(regions: &[PixelRect<f32>]) -> (Path, Rect) {
|
||||
let silhouette = regions
|
||||
.iter()
|
||||
.map(|r| Path::rect(to_skia_rect(r), None))
|
||||
.reduce(|a, b| a.op(&b, PathOp::Union).unwrap())
|
||||
.unwrap();
|
||||
let bounding_rect = regions
|
||||
.iter()
|
||||
.map(to_skia_rect)
|
||||
.reduce(Rect::join2)
|
||||
.unwrap();
|
||||
|
||||
(silhouette, bounding_rect)
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
|||
|
||||
use skia_safe::{
|
||||
canvas::SaveLayerRec,
|
||||
image_filters::blur,
|
||||
utils::shadow_utils::{draw_shadow, ShadowFlags},
|
||||
BlendMode, Canvas, ClipOp, Color, Matrix, Paint, Path, Picture, PictureRecorder, Point3, Rect,
|
||||
};
|
||||
|
|
@ -13,7 +12,7 @@ use crate::{
|
|||
profiling::{tracy_plot, tracy_zone},
|
||||
renderer::{animation_utils::*, GridRenderer, RendererSettings},
|
||||
settings::SETTINGS,
|
||||
units::{to_skia_rect, GridPos, GridRect, GridScale, GridSize, PixelRect},
|
||||
units::{to_skia_rect, GridPos, GridRect, GridScale, GridSize, PixelRect, PixelVec},
|
||||
utils::RingBuffer,
|
||||
};
|
||||
|
||||
|
|
@ -72,8 +71,8 @@ struct Line {
|
|||
line_fragments: Vec<LineFragment>,
|
||||
background_picture: Option<Picture>,
|
||||
foreground_picture: Option<Picture>,
|
||||
has_transparency: bool,
|
||||
is_inferred_border: bool,
|
||||
blend: u8,
|
||||
is_valid: bool,
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +103,7 @@ pub struct RenderedWindow {
|
|||
pub struct WindowDrawDetails {
|
||||
pub id: u64,
|
||||
pub region: PixelRect<f32>,
|
||||
pub floating_order: Option<u64>,
|
||||
}
|
||||
|
||||
impl WindowDrawDetails {
|
||||
|
|
@ -116,6 +116,19 @@ impl WindowDrawDetails {
|
|||
}
|
||||
}
|
||||
|
||||
impl Line {
|
||||
fn update_background_blend(&mut self, blend: u8) {
|
||||
if self.blend != blend {
|
||||
self.blend = blend;
|
||||
self.is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn has_transparency(&self) -> bool {
|
||||
self.blend > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderedWindow {
|
||||
pub fn new(id: u64, grid_position: GridPos<i32>, grid_size: GridSize<u32>) -> RenderedWindow {
|
||||
RenderedWindow {
|
||||
|
|
@ -151,6 +164,13 @@ impl RenderedWindow {
|
|||
* grid_scale
|
||||
}
|
||||
|
||||
pub fn update_blend(&self, blend: u8) {
|
||||
for (_, line) in self.iter_lines() {
|
||||
let mut line = line.borrow_mut();
|
||||
line.update_background_blend(blend);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_target_position(&self, grid_rect: &GridRect<f32>) -> GridPos<f32> {
|
||||
let destination = self.grid_destination + grid_rect.min.to_vector();
|
||||
|
||||
|
|
@ -221,126 +241,72 @@ impl RenderedWindow {
|
|||
animating
|
||||
}
|
||||
|
||||
pub fn draw_surface(
|
||||
pub fn draw_background_surface(
|
||||
&mut self,
|
||||
canvas: &Canvas,
|
||||
pixel_region: &Rect,
|
||||
pixel_region: PixelRect<f32>,
|
||||
grid_scale: GridScale,
|
||||
default_background: Color,
|
||||
) {
|
||||
let scroll_offset_lines = self.scroll_animation.position.floor();
|
||||
let scroll_offset = scroll_offset_lines - self.scroll_animation.position;
|
||||
let scroll_offset_lines = scroll_offset_lines as isize;
|
||||
let scroll_offset_pixels = (scroll_offset * grid_scale.0.height).round() as isize;
|
||||
let line_height = grid_scale.0.height;
|
||||
let mut has_transparency = false;
|
||||
|
||||
let lines: Vec<(Matrix, &Rc<RefCell<Line>>)> = if !self.scrollback_lines.is_empty() {
|
||||
(0..self.grid_size.height as isize + 1)
|
||||
.filter_map(|i| {
|
||||
self.scrollback_lines[scroll_offset_lines + i]
|
||||
.as_ref()
|
||||
.map(|line| (i, line))
|
||||
})
|
||||
.map(|(i, line)| {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.set_translate((
|
||||
pixel_region.left(),
|
||||
pixel_region.top()
|
||||
+ (scroll_offset_pixels
|
||||
+ ((i + self.viewport_margins.top as isize)
|
||||
* grid_scale.0.height as isize))
|
||||
as f32,
|
||||
));
|
||||
(matrix, line)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let inner_region = self.inner_region(pixel_region, grid_scale);
|
||||
|
||||
let top_border_indices = 0..self.viewport_margins.top as isize;
|
||||
let actual_line_count = self.actual_lines.len() as isize;
|
||||
let bottom_border_indices =
|
||||
actual_line_count - self.viewport_margins.bottom as isize..actual_line_count;
|
||||
let margins_inferred = self.viewport_margins.inferred;
|
||||
|
||||
let border_lines: Vec<_> = top_border_indices
|
||||
.chain(bottom_border_indices)
|
||||
.filter_map(|i| {
|
||||
self.actual_lines[i].as_ref().and_then(|line| {
|
||||
if !margins_inferred || line.borrow().is_inferred_border {
|
||||
Some((i, line))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.map(|(i, line)| {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.set_translate((
|
||||
pixel_region.left(),
|
||||
pixel_region.top() + (i * grid_scale.0.height as isize) as f32,
|
||||
));
|
||||
(matrix, line)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let inner_region = Rect::from_xywh(
|
||||
pixel_region.x(),
|
||||
pixel_region.y() + self.viewport_margins.top as f32 * line_height,
|
||||
pixel_region.width(),
|
||||
pixel_region.height()
|
||||
- (self.viewport_margins.top + self.viewport_margins.bottom) as f32 * line_height,
|
||||
canvas.save();
|
||||
canvas.clip_rect(to_skia_rect(&pixel_region), None, false);
|
||||
for (matrix, line) in self.iter_border_lines_with_transform(pixel_region, grid_scale) {
|
||||
let line = line.borrow();
|
||||
if let Some(background_picture) = &line.background_picture {
|
||||
has_transparency |= line.has_transparency();
|
||||
canvas.draw_picture(background_picture, Some(&matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.save();
|
||||
canvas.clip_rect(inner_region, None, false);
|
||||
let mut pics = 0;
|
||||
for (matrix, line) in self.iter_scrollable_lines_with_transform(pixel_region, grid_scale) {
|
||||
let line = line.borrow();
|
||||
if let Some(background_picture) = &line.background_picture {
|
||||
has_transparency |= line.has_transparency();
|
||||
canvas.draw_picture(background_picture, Some(&matrix), None);
|
||||
pics += 1;
|
||||
}
|
||||
}
|
||||
log::trace!(
|
||||
"region: {:?}, inner: {:?}, pics: {}",
|
||||
pixel_region,
|
||||
inner_region,
|
||||
pics
|
||||
);
|
||||
|
||||
let mut background_paint = Paint::default();
|
||||
background_paint.set_blend_mode(BlendMode::Src);
|
||||
background_paint.set_alpha(default_background.a());
|
||||
|
||||
let save_layer_rec = SaveLayerRec::default()
|
||||
.bounds(pixel_region)
|
||||
.paint(&background_paint);
|
||||
canvas.save_layer(&save_layer_rec);
|
||||
canvas.clear(default_background.with_a(255));
|
||||
for (matrix, line) in &border_lines {
|
||||
let line = line.borrow();
|
||||
if let Some(background_picture) = &line.background_picture {
|
||||
has_transparency |= line.has_transparency;
|
||||
canvas.draw_picture(background_picture, Some(matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.save();
|
||||
canvas.clip_rect(inner_region, None, false);
|
||||
for (matrix, line) in &lines {
|
||||
let line = line.borrow();
|
||||
if let Some(background_picture) = &line.background_picture {
|
||||
has_transparency |= line.has_transparency;
|
||||
canvas.draw_picture(background_picture, Some(matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.restore();
|
||||
|
||||
for (matrix, line) in &border_lines {
|
||||
let line = line.borrow();
|
||||
if let Some(foreground_picture) = &line.foreground_picture {
|
||||
canvas.draw_picture(foreground_picture, Some(matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.save();
|
||||
canvas.clip_rect(inner_region, None, false);
|
||||
for (matrix, line) in &lines {
|
||||
let line = line.borrow();
|
||||
if let Some(foreground_picture) = &line.foreground_picture {
|
||||
canvas.draw_picture(foreground_picture, Some(matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
self.has_transparency = has_transparency;
|
||||
}
|
||||
|
||||
fn has_transparency(&self) -> bool {
|
||||
pub fn draw_foreground_surface(
|
||||
&mut self,
|
||||
canvas: &Canvas,
|
||||
pixel_region: PixelRect<f32>,
|
||||
grid_scale: GridScale,
|
||||
) {
|
||||
for (matrix, line) in self.iter_border_lines_with_transform(pixel_region, grid_scale) {
|
||||
let line = line.borrow();
|
||||
if let Some(foreground_picture) = &line.foreground_picture {
|
||||
canvas.draw_picture(foreground_picture, Some(&matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.save();
|
||||
canvas.clip_rect(self.inner_region(pixel_region, grid_scale), None, false);
|
||||
for (matrix, line) in self.iter_scrollable_lines_with_transform(pixel_region, grid_scale) {
|
||||
let line = line.borrow();
|
||||
if let Some(foreground_picture) = &line.foreground_picture {
|
||||
canvas.draw_picture(foreground_picture, Some(&matrix), None);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
pub fn has_transparency(&self) -> bool {
|
||||
let scroll_offset_lines = self.scroll_animation.position.floor() as isize;
|
||||
if self.scrollback_lines.is_empty() {
|
||||
return false;
|
||||
|
|
@ -350,7 +316,7 @@ impl RenderedWindow {
|
|||
scroll_offset_lines..scroll_offset_lines + self.grid_size.height as isize + 1,
|
||||
)
|
||||
.flatten()
|
||||
.any(|line| line.borrow().has_transparency)
|
||||
.any(|line| line.borrow().has_transparency())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
|
|
@ -359,20 +325,11 @@ impl RenderedWindow {
|
|||
settings: &RendererSettings,
|
||||
default_background: Color,
|
||||
grid_scale: GridScale,
|
||||
previous_floating_rects: &mut Vec<PixelRect<f32>>,
|
||||
) -> WindowDrawDetails {
|
||||
let has_transparency = default_background.a() != 255 || self.has_transparency();
|
||||
|
||||
let pixel_region_box = self.pixel_region(grid_scale);
|
||||
let pixel_region = to_skia_rect(&pixel_region_box);
|
||||
let transparent_floating = self.anchor_info.is_some() && has_transparency;
|
||||
|
||||
if self.anchor_info.is_some()
|
||||
&& settings.floating_shadow
|
||||
&& !previous_floating_rects
|
||||
.iter()
|
||||
.any(|rect| rect.contains_box(&pixel_region_box))
|
||||
{
|
||||
if self.anchor_info.is_some() && settings.floating_shadow {
|
||||
root_canvas.save();
|
||||
let shadow_path = Path::rect(pixel_region, None);
|
||||
// We clip using the Difference op to make sure that the shadow isn't rendered inside
|
||||
|
|
@ -400,35 +357,10 @@ impl RenderedWindow {
|
|||
Some(ShadowFlags::DIRECTIONAL_LIGHT),
|
||||
);
|
||||
root_canvas.restore();
|
||||
previous_floating_rects.push(pixel_region_box);
|
||||
}
|
||||
|
||||
root_canvas.save();
|
||||
root_canvas.clip_rect(pixel_region, None, Some(false));
|
||||
let need_blur = transparent_floating && settings.floating_blur;
|
||||
|
||||
if need_blur {
|
||||
if let Some(blur) = blur(
|
||||
(
|
||||
settings.floating_blur_amount_x,
|
||||
settings.floating_blur_amount_y,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
let paint = Paint::default()
|
||||
.set_anti_alias(false)
|
||||
.set_blend_mode(BlendMode::Src)
|
||||
.to_owned();
|
||||
let save_layer_rec = SaveLayerRec::default()
|
||||
.backdrop(&blur)
|
||||
.bounds(&pixel_region)
|
||||
.paint(&paint);
|
||||
root_canvas.save_layer(&save_layer_rec);
|
||||
root_canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
let paint = Paint::default()
|
||||
.set_anti_alias(false)
|
||||
|
|
@ -442,7 +374,19 @@ impl RenderedWindow {
|
|||
|
||||
let save_layer_rec = SaveLayerRec::default().bounds(&pixel_region).paint(&paint);
|
||||
root_canvas.save_layer(&save_layer_rec);
|
||||
self.draw_surface(root_canvas, &pixel_region, grid_scale, default_background);
|
||||
|
||||
let mut background_paint = Paint::default();
|
||||
background_paint.set_blend_mode(BlendMode::Src);
|
||||
background_paint.set_alpha(default_background.a());
|
||||
let background_layer_rec = SaveLayerRec::default()
|
||||
.bounds(&pixel_region)
|
||||
.paint(&background_paint);
|
||||
|
||||
root_canvas.save_layer(&background_layer_rec);
|
||||
root_canvas.clear(default_background.with_a(255));
|
||||
self.draw_background_surface(root_canvas, pixel_region_box, grid_scale);
|
||||
root_canvas.restore();
|
||||
self.draw_foreground_surface(root_canvas, pixel_region_box, grid_scale);
|
||||
root_canvas.restore();
|
||||
|
||||
root_canvas.restore();
|
||||
|
|
@ -450,6 +394,7 @@ impl RenderedWindow {
|
|||
WindowDrawDetails {
|
||||
id: self.id,
|
||||
region: pixel_region_box,
|
||||
floating_order: self.anchor_info.as_ref().map(|v| v.sort_order),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -516,8 +461,8 @@ impl RenderedWindow {
|
|||
line_fragments,
|
||||
background_picture: None,
|
||||
foreground_picture: None,
|
||||
has_transparency: false,
|
||||
is_inferred_border: false,
|
||||
blend: 0,
|
||||
is_valid: false,
|
||||
};
|
||||
|
||||
|
|
@ -693,6 +638,123 @@ impl RenderedWindow {
|
|||
self.scroll_delta = 0;
|
||||
}
|
||||
|
||||
fn iter_border_lines(&self) -> impl Iterator<Item = (isize, &Rc<RefCell<Line>>)> {
|
||||
let top_border_indices = 0..self.viewport_margins.top as isize;
|
||||
let actual_line_count = self.actual_lines.len() as isize;
|
||||
let bottom_border_indices =
|
||||
actual_line_count - self.viewport_margins.bottom as isize..actual_line_count;
|
||||
let margins_inferred = self.viewport_margins.inferred;
|
||||
|
||||
top_border_indices
|
||||
.chain(bottom_border_indices)
|
||||
.filter_map(move |i| {
|
||||
self.actual_lines[i].as_ref().and_then(|line| {
|
||||
if !margins_inferred || line.borrow().is_inferred_border {
|
||||
Some((i, line))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Iterates over the scrollable lines (excluding the viewport margins). Includes the index for
|
||||
// the given line being scrolled
|
||||
fn iter_scrollable_lines(&self) -> impl Iterator<Item = (isize, &Rc<RefCell<Line>>)> {
|
||||
let scroll_offset_lines = self.scroll_animation.position.floor();
|
||||
let scroll_offset_lines = scroll_offset_lines as isize;
|
||||
|
||||
let line_indices = if !self.scrollback_lines.is_empty() {
|
||||
0..self.grid_size.height as isize + 1
|
||||
} else {
|
||||
0..0
|
||||
};
|
||||
|
||||
line_indices.filter_map(move |i| {
|
||||
self.scrollback_lines[scroll_offset_lines + i]
|
||||
.as_ref()
|
||||
.map(|line| (i, line))
|
||||
})
|
||||
}
|
||||
|
||||
fn iter_lines(&self) -> impl Iterator<Item = (isize, &Rc<RefCell<Line>>)> {
|
||||
self.iter_border_lines().chain(self.iter_scrollable_lines())
|
||||
}
|
||||
|
||||
fn iter_scrollable_lines_with_transform(
|
||||
&self,
|
||||
pixel_region: PixelRect<f32>,
|
||||
grid_scale: GridScale,
|
||||
) -> impl Iterator<Item = (Matrix, &Rc<RefCell<Line>>)> {
|
||||
let scroll_offset_lines = self.scroll_animation.position.floor();
|
||||
let scroll_offset = scroll_offset_lines - self.scroll_animation.position;
|
||||
let scroll_offset_pixels = (scroll_offset * grid_scale.height()).round() as isize;
|
||||
|
||||
self.iter_scrollable_lines().map(move |(i, line)| {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.set_translate((
|
||||
pixel_region.min.x,
|
||||
pixel_region.min.y
|
||||
+ (scroll_offset_pixels
|
||||
+ ((i + self.viewport_margins.top as isize) * grid_scale.height() as isize))
|
||||
as f32,
|
||||
));
|
||||
(matrix, line)
|
||||
})
|
||||
}
|
||||
|
||||
fn iter_border_lines_with_transform(
|
||||
&self,
|
||||
pixel_region: PixelRect<f32>,
|
||||
grid_scale: GridScale,
|
||||
) -> impl Iterator<Item = (Matrix, &Rc<RefCell<Line>>)> {
|
||||
self.iter_border_lines().map(move |(i, line)| {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.set_translate((
|
||||
pixel_region.min.x,
|
||||
pixel_region.min.y + (i * grid_scale.height() as isize) as f32,
|
||||
));
|
||||
(matrix, line)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the rect containing the region of the window that does not have borders above and
|
||||
/// below it. Note: This does not take into account the borders on the left and the right of
|
||||
/// the window.
|
||||
pub fn inner_region(&self, pixel_region: PixelRect<f32>, grid_scale: GridScale) -> Rect {
|
||||
let line_height = grid_scale.height();
|
||||
let adjusted_region = PixelRect::new(
|
||||
pixel_region.min + PixelVec::new(0., self.viewport_margins.top as f32 * line_height),
|
||||
pixel_region.max
|
||||
+ PixelVec::new(
|
||||
0.,
|
||||
(self.viewport_margins.top - self.viewport_margins.bottom) as f32 * line_height,
|
||||
),
|
||||
);
|
||||
|
||||
to_skia_rect(&adjusted_region)
|
||||
}
|
||||
|
||||
pub fn get_smallest_blend_value(&self) -> Option<u8> {
|
||||
let height = self.grid_size.height as isize;
|
||||
if height == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut smallest_blend_value: Option<u8> = None;
|
||||
|
||||
for (_, line) in self.iter_lines() {
|
||||
let line = line.borrow();
|
||||
line.line_fragments.iter().for_each(|f| {
|
||||
if let Some(style) = &f.style {
|
||||
smallest_blend_value =
|
||||
Some(smallest_blend_value.map_or(style.blend, |v| v.min(style.blend)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
smallest_blend_value
|
||||
}
|
||||
|
||||
pub fn prepare_lines(&mut self, grid_renderer: &mut GridRenderer) {
|
||||
let scroll_offset_lines = self.scroll_animation.position.floor() as isize;
|
||||
let height = self.grid_size.height as isize;
|
||||
|
|
@ -713,7 +775,7 @@ impl RenderedWindow {
|
|||
let grid_rect = Rect::from_wh(line_size.width, line_size.height);
|
||||
let canvas = recorder.begin_recording(grid_rect, None);
|
||||
|
||||
let mut has_transparency = false;
|
||||
let mut blend = 0;
|
||||
let mut custom_background = false;
|
||||
|
||||
for line_fragment in line.line_fragments.iter() {
|
||||
|
|
@ -731,7 +793,7 @@ impl RenderedWindow {
|
|||
style,
|
||||
);
|
||||
custom_background |= background_info.custom_color;
|
||||
has_transparency |= background_info.transparent;
|
||||
blend = blend.min(style.as_ref().map_or(0, |s| s.blend));
|
||||
}
|
||||
let background_picture =
|
||||
custom_background.then_some(recorder.finish_recording_as_picture(None).unwrap());
|
||||
|
|
@ -760,7 +822,7 @@ impl RenderedWindow {
|
|||
|
||||
line.background_picture = background_picture;
|
||||
line.foreground_picture = foreground_picture;
|
||||
line.has_transparency = has_transparency;
|
||||
line.blend = blend;
|
||||
line.is_valid = true;
|
||||
};
|
||||
|
||||
|
|
|
|||
10
src/units.rs
10
src/units.rs
|
|
@ -36,6 +36,16 @@ pub fn to_skia_rect(rect: &PixelRect<f32>) -> skia_safe::Rect {
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct GridScale(pub PixelSize<f32>);
|
||||
|
||||
impl GridScale {
|
||||
pub fn height(&self) -> f32 {
|
||||
self.0.height
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.0.width
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<GridScale> for GridVec<f32> {
|
||||
type Output = PixelVec<f32>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
mod ring_buffer;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
pub use ring_buffer::*;
|
||||
#[cfg(test)]
|
||||
pub use test::*;
|
||||
|
||||
pub fn is_tty() -> bool {
|
||||
use std::io::IsTerminal;
|
||||
|
|
|
|||
218
src/utils/test.rs
Normal file
218
src/utils/test.rs
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use indoc::indoc;
|
||||
use lazy_static::lazy_static;
|
||||
use skia_safe::{Point, Rect};
|
||||
|
||||
lazy_static! {
|
||||
static ref IGNOREABLE_CHARACTERS: HashSet<char> =
|
||||
['-', '|', '*', '+', ' '].iter().cloned().collect();
|
||||
}
|
||||
|
||||
/// Helper function to convert ascii art into a list of rectangles.
|
||||
/// Each rectangle must have at least two corners labeled in opposite corners to work
|
||||
/// properly.
|
||||
/// NOTE: This function returns rectangles that CONTAIN the labels. So when using this function in
|
||||
/// conjunction with ascii_to_points, you may need to push the outer corners out by one index to
|
||||
/// get the expected results
|
||||
/// Returns rects in order by label.
|
||||
pub fn ascii_to_rects(ascii: &str) -> Vec<Rect> {
|
||||
// Split the ascii into lines and characters. Loop over the characters building
|
||||
// a list of coordinates by character. After all the text is iterated over, find the min
|
||||
// and max coordinate for each and return them as rects.
|
||||
let mut points_by_label = BTreeMap::new();
|
||||
for (y, line) in ascii.lines().enumerate() {
|
||||
for (x, c) in line.chars().enumerate() {
|
||||
if IGNOREABLE_CHARACTERS.contains(&c) {
|
||||
continue;
|
||||
}
|
||||
points_by_label
|
||||
.entry(c)
|
||||
.or_insert_with(Vec::new)
|
||||
.push((x, y));
|
||||
}
|
||||
}
|
||||
|
||||
let mut rects = vec![];
|
||||
for points in points_by_label.values() {
|
||||
let min_x = points.iter().map(|(x, _)| x).min().unwrap();
|
||||
let min_y = points.iter().map(|(_, y)| y).min().unwrap();
|
||||
let max_x = points.iter().map(|(x, _)| x).max().unwrap();
|
||||
let max_y = points.iter().map(|(_, y)| y).max().unwrap();
|
||||
|
||||
rects.push(Rect::from_xywh(
|
||||
*min_x as f32,
|
||||
*min_y as f32,
|
||||
(max_x - min_x + 1) as f32,
|
||||
(max_y - min_y + 1) as f32,
|
||||
));
|
||||
}
|
||||
|
||||
rects
|
||||
}
|
||||
|
||||
/// Helper function to convert ascii art into a list of points ordered by their label.
|
||||
pub fn ascii_to_points(ascii: &str) -> Vec<Point> {
|
||||
let mut points = BTreeMap::new();
|
||||
for (y, line) in ascii.lines().enumerate() {
|
||||
for (x, c) in line.chars().enumerate() {
|
||||
if IGNOREABLE_CHARACTERS.contains(&c) {
|
||||
continue;
|
||||
}
|
||||
points.entry(c).or_insert_with(Vec::new).push((x, y));
|
||||
}
|
||||
}
|
||||
|
||||
points
|
||||
.values()
|
||||
.flat_map(|points| points.iter().map(|(x, y)| Point::new(*x as f32, *y as f32)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn assert_points_eq(actual: Vec<Point>, expected_ascii: &str) {
|
||||
let expected = ascii_to_points(expected_ascii);
|
||||
if expected.len() != actual.len() || expected.iter().zip(actual.iter()).any(|(a, b)| a != b) {
|
||||
let actual_ascii = points_to_ascii(actual);
|
||||
panic!(
|
||||
indoc! {"
|
||||
Points do not match
|
||||
Expected:
|
||||
{}
|
||||
Actual:
|
||||
{}
|
||||
"},
|
||||
expected_ascii, actual_ascii
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn points_to_ascii(points: Vec<Point>) -> String {
|
||||
if points
|
||||
.iter()
|
||||
.any(|point| point.x.fract() != 0.0 || point.y.fract() != 0.)
|
||||
{
|
||||
panic!("Points must be integers to render as ascii");
|
||||
}
|
||||
|
||||
let line_width = points.iter().map(|p| p.x as usize).max().unwrap() + 1;
|
||||
let line_count = points.iter().map(|p| p.y as usize).max().unwrap() + 1;
|
||||
let mut ascii = vec![vec![' '; line_width as usize]; line_count as usize];
|
||||
let numbers_big_enough = points.len() <= 9;
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (index, p) in points.iter().enumerate() {
|
||||
let char = if numbers_big_enough {
|
||||
std::char::from_digit(index as u32, 10).unwrap()
|
||||
} else {
|
||||
chars.chars().nth(index).expect("Too many points")
|
||||
};
|
||||
ascii[p.y as usize][p.x as usize] = char;
|
||||
}
|
||||
ascii
|
||||
.iter()
|
||||
.map(|line| line.iter().collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii_to_rect_works() {
|
||||
// Single rect
|
||||
assert_eq!(
|
||||
ascii_to_rects(indoc! {"
|
||||
1---1
|
||||
| |
|
||||
1---1
|
||||
"}),
|
||||
vec![Rect::from_xywh(0., 0., 5., 3.)]
|
||||
);
|
||||
|
||||
// Overlapping rects
|
||||
assert_eq!(
|
||||
ascii_to_rects(indoc! {"
|
||||
1---1
|
||||
| 2-+-2
|
||||
1-+-1 |
|
||||
2---2
|
||||
"}),
|
||||
vec![
|
||||
Rect::from_xywh(0., 0., 5., 3.),
|
||||
Rect::from_xywh(2., 1., 5., 3.),
|
||||
]
|
||||
);
|
||||
|
||||
// Overlapping rects with shared corner
|
||||
assert_eq!(
|
||||
ascii_to_rects(indoc! {"
|
||||
1----1
|
||||
| |
|
||||
*--2-1
|
||||
| |
|
||||
2--2
|
||||
"}),
|
||||
vec![
|
||||
Rect::from_xywh(0., 0., 6., 3.),
|
||||
Rect::from_xywh(0., 2., 4., 3.),
|
||||
]
|
||||
);
|
||||
|
||||
// Adjacent rects
|
||||
assert_eq!(
|
||||
ascii_to_rects(indoc! {"
|
||||
1----1
|
||||
| |
|
||||
1----1
|
||||
2--2
|
||||
| |
|
||||
2--2
|
||||
"}),
|
||||
vec![
|
||||
Rect::from_xywh(0., 0., 6., 3.),
|
||||
Rect::from_xywh(0., 3., 4., 3.),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii_to_points_works() {
|
||||
// Single point
|
||||
assert_eq!(
|
||||
ascii_to_points(indoc! {"
|
||||
1
|
||||
"}),
|
||||
vec![Point::new(0., 0.)]
|
||||
);
|
||||
|
||||
// Rectangle
|
||||
assert_eq!(
|
||||
ascii_to_points(indoc! {"
|
||||
1-2
|
||||
| |
|
||||
3-4
|
||||
"}),
|
||||
vec![
|
||||
Point::new(0., 0.),
|
||||
Point::new(2., 0.),
|
||||
Point::new(0., 2.),
|
||||
Point::new(2., 2.),
|
||||
]
|
||||
);
|
||||
|
||||
// More complicated shape
|
||||
assert_eq!(
|
||||
ascii_to_points(indoc! {"
|
||||
1-2
|
||||
| |
|
||||
| 3-4
|
||||
| |
|
||||
6---5
|
||||
"}),
|
||||
vec![
|
||||
Point::new(0., 0.),
|
||||
Point::new(2., 0.),
|
||||
Point::new(2., 2.),
|
||||
Point::new(4., 2.),
|
||||
Point::new(4., 4.),
|
||||
Point::new(0., 4.),
|
||||
]
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue