Smooth scrolling (#423)

* parse viewport events

* progress toward pixel scrolling

* ITS WORKING

* fix basic scrolling gaps

* add limit to number of old surfaces to store

* set cursor destination based on rendered location not global grid
macos-click-through
Keith Simmons 4 years ago committed by GitHub
parent 38c5538186
commit 1d981f3c2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -216,6 +216,13 @@ pub enum RedrawEvent {
scrolled: bool, scrolled: bool,
separator_character: String, separator_character: String,
}, },
WindowViewport {
grid: u64,
top_line: f64,
bottom_line: f64,
current_line: f64,
current_column: f64,
},
CommandLineShow { CommandLineShow {
content: StyledContent, content: StyledContent,
position: u64, position: u64,
@ -732,6 +739,27 @@ fn parse_msg_set_pos(msg_set_pos_arguments: Vec<Value>) -> Result<RedrawEvent> {
}) })
} }
fn parse_win_viewport(win_viewport_arguments: Vec<Value>) -> Result<RedrawEvent> {
let values = [
Value::Nil,
Value::Nil,
Value::Nil,
Value::Nil,
Value::Nil,
Value::Nil,
];
let [grid, _window, top_line, bottom_line, current_line, current_column] =
extract_values(win_viewport_arguments, values)?;
Ok(RedrawEvent::WindowViewport {
grid: parse_u64(grid)?,
top_line: parse_f64(top_line)?,
bottom_line: parse_f64(bottom_line)?,
current_line: parse_f64(current_line)?,
current_column: parse_f64(current_column)?,
})
}
fn parse_styled_content(line: Value) -> Result<StyledContent> { fn parse_styled_content(line: Value) -> Result<StyledContent> {
parse_array(line)? parse_array(line)?
.into_iter() .into_iter()
@ -898,6 +926,7 @@ pub fn parse_redraw_event(event_value: Value) -> Result<Vec<RedrawEvent>> {
"win_hide" => Some(parse_win_hide(event_parameters)?), "win_hide" => Some(parse_win_hide(event_parameters)?),
"win_close" => Some(parse_win_close(event_parameters)?), "win_close" => Some(parse_win_close(event_parameters)?),
"msg_set_pos" => Some(parse_msg_set_pos(event_parameters)?), "msg_set_pos" => Some(parse_msg_set_pos(event_parameters)?),
"win_viewport" => Some(parse_win_viewport(event_parameters)?),
"cmdline_show" => Some(parse_cmdline_show(event_parameters)?), "cmdline_show" => Some(parse_cmdline_show(event_parameters)?),
"cmdline_pos" => Some(parse_cmdline_pos(event_parameters)?), "cmdline_pos" => Some(parse_cmdline_pos(event_parameters)?),
"cmdline_special_char" => Some(parse_cmdline_special_char(event_parameters)?), "cmdline_special_char" => Some(parse_cmdline_special_char(event_parameters)?),

@ -1,240 +1,238 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use skulpin::skia_safe::Color4f; use skulpin::skia_safe::Color4f;
use super::style::{Colors, Style}; use super::style::{Colors, Style};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum CursorShape { pub enum CursorShape {
Block, Block,
Horizontal, Horizontal,
Vertical, Vertical,
} }
impl CursorShape { impl CursorShape {
pub fn from_type_name(name: &str) -> Option<CursorShape> { pub fn from_type_name(name: &str) -> Option<CursorShape> {
match name { match name {
"block" => Some(CursorShape::Block), "block" => Some(CursorShape::Block),
"horizontal" => Some(CursorShape::Horizontal), "horizontal" => Some(CursorShape::Horizontal),
"vertical" => Some(CursorShape::Vertical), "vertical" => Some(CursorShape::Vertical),
_ => None, _ => None,
} }
} }
} }
#[derive(Default, Debug, Clone, PartialEq)] #[derive(Default, Debug, Clone, PartialEq)]
pub struct CursorMode { pub struct CursorMode {
pub shape: Option<CursorShape>, pub shape: Option<CursorShape>,
pub style_id: Option<u64>, pub style_id: Option<u64>,
pub cell_percentage: Option<f32>, pub cell_percentage: Option<f32>,
pub blinkwait: Option<u64>, pub blinkwait: Option<u64>,
pub blinkon: Option<u64>, pub blinkon: Option<u64>,
pub blinkoff: Option<u64>, pub blinkoff: Option<u64>,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Cursor { pub struct Cursor {
pub position: (f64, f64), pub grid_position: (u64, u64),
pub grid_position: (u64, u64), pub parent_window_id: u64,
pub parent_window_id: u64, pub shape: CursorShape,
pub shape: CursorShape, pub cell_percentage: Option<f32>,
pub cell_percentage: Option<f32>, pub blinkwait: Option<u64>,
pub blinkwait: Option<u64>, pub blinkon: Option<u64>,
pub blinkon: Option<u64>, pub blinkoff: Option<u64>,
pub blinkoff: Option<u64>, pub style: Option<Arc<Style>>,
pub style: Option<Arc<Style>>, pub enabled: bool,
pub enabled: bool, pub double_width: bool,
pub double_width: bool, pub character: String,
pub character: String, }
}
impl Cursor {
impl Cursor { pub fn new() -> Cursor {
pub fn new() -> Cursor { Cursor {
Cursor { grid_position: (0, 0),
position: (0.0, 0.0), parent_window_id: 0,
grid_position: (0, 0), shape: CursorShape::Block,
parent_window_id: 0, style: None,
shape: CursorShape::Block, cell_percentage: None,
style: None, blinkwait: None,
cell_percentage: None, blinkon: None,
blinkwait: None, blinkoff: None,
blinkon: None, enabled: true,
blinkoff: None, double_width: false,
enabled: true, character: " ".to_string(),
double_width: false, }
character: " ".to_string(), }
}
} pub fn foreground(&self, default_colors: &Colors) -> Color4f {
if let Some(style) = &self.style {
pub fn foreground(&self, default_colors: &Colors) -> Color4f { style
if let Some(style) = &self.style { .colors
style .foreground
.colors .clone()
.foreground .unwrap_or_else(|| default_colors.background.clone().unwrap())
.clone() } else {
.unwrap_or_else(|| default_colors.background.clone().unwrap()) default_colors.background.clone().unwrap()
} else { }
default_colors.background.clone().unwrap() }
}
} pub fn background(&self, default_colors: &Colors) -> Color4f {
if let Some(style) = &self.style {
pub fn background(&self, default_colors: &Colors) -> Color4f { style
if let Some(style) = &self.style { .colors
style .background
.colors .clone()
.background .unwrap_or_else(|| default_colors.foreground.clone().unwrap())
.clone() } else {
.unwrap_or_else(|| default_colors.foreground.clone().unwrap()) default_colors.foreground.clone().unwrap()
} else { }
default_colors.foreground.clone().unwrap() }
}
} pub fn change_mode(&mut self, cursor_mode: &CursorMode, styles: &HashMap<u64, Arc<Style>>) {
let CursorMode {
pub fn change_mode(&mut self, cursor_mode: &CursorMode, styles: &HashMap<u64, Arc<Style>>) { shape,
let CursorMode { style_id,
shape, cell_percentage,
style_id, blinkwait,
cell_percentage, blinkon,
blinkwait, blinkoff,
blinkon, } = cursor_mode;
blinkoff,
} = cursor_mode; if let Some(shape) = shape {
self.shape = shape.clone();
if let Some(shape) = shape { }
self.shape = shape.clone();
} if let Some(style_id) = style_id {
self.style = styles.get(style_id).cloned();
if let Some(style_id) = style_id { }
self.style = styles.get(style_id).cloned();
} self.cell_percentage = *cell_percentage;
self.blinkwait = *blinkwait;
self.cell_percentage = *cell_percentage; self.blinkon = *blinkon;
self.blinkwait = *blinkwait; self.blinkoff = *blinkoff;
self.blinkon = *blinkon; }
self.blinkoff = *blinkoff; }
}
} #[cfg(test)]
mod tests {
#[cfg(test)] use super::*;
mod tests {
use super::*; const COLORS: Colors = Colors {
foreground: Some(Color4f::new(0.1, 0.1, 0.1, 0.1)),
const COLORS: Colors = Colors { background: Some(Color4f::new(0.2, 0.1, 0.1, 0.1)),
foreground: Some(Color4f::new(0.1, 0.1, 0.1, 0.1)), special: Some(Color4f::new(0.3, 0.1, 0.1, 0.1)),
background: Some(Color4f::new(0.2, 0.1, 0.1, 0.1)), };
special: Some(Color4f::new(0.3, 0.1, 0.1, 0.1)),
}; const DEFAULT_COLORS: Colors = Colors {
foreground: Some(Color4f::new(0.1, 0.2, 0.1, 0.1)),
const DEFAULT_COLORS: Colors = Colors { background: Some(Color4f::new(0.2, 0.2, 0.1, 0.1)),
foreground: Some(Color4f::new(0.1, 0.2, 0.1, 0.1)), special: Some(Color4f::new(0.3, 0.2, 0.1, 0.1)),
background: Some(Color4f::new(0.2, 0.2, 0.1, 0.1)), };
special: Some(Color4f::new(0.3, 0.2, 0.1, 0.1)),
}; const NONE_COLORS: Colors = Colors {
foreground: None,
const NONE_COLORS: Colors = Colors { background: None,
foreground: None, special: None,
background: None, };
special: None,
}; #[test]
fn test_from_type_name() {
#[test] assert_eq!(
fn test_from_type_name() { CursorShape::from_type_name("block"),
assert_eq!( Some(CursorShape::Block)
CursorShape::from_type_name("block"), );
Some(CursorShape::Block) assert_eq!(
); CursorShape::from_type_name("horizontal"),
assert_eq!( Some(CursorShape::Horizontal)
CursorShape::from_type_name("horizontal"), );
Some(CursorShape::Horizontal) assert_eq!(
); CursorShape::from_type_name("vertical"),
assert_eq!( Some(CursorShape::Vertical)
CursorShape::from_type_name("vertical"), );
Some(CursorShape::Vertical) }
);
} #[test]
fn test_foreground() {
#[test] let mut cursor = Cursor::new();
fn test_foreground() { let style = Some(Arc::new(Style::new(COLORS)));
let mut cursor = Cursor::new();
let style = Some(Arc::new(Style::new(COLORS))); assert_eq!(
cursor.foreground(&DEFAULT_COLORS),
assert_eq!( DEFAULT_COLORS.background.clone().unwrap()
cursor.foreground(&DEFAULT_COLORS), );
DEFAULT_COLORS.background.clone().unwrap() cursor.style = style.clone();
); assert_eq!(
cursor.style = style.clone(); cursor.foreground(&DEFAULT_COLORS),
assert_eq!( COLORS.foreground.clone().unwrap()
cursor.foreground(&DEFAULT_COLORS), );
COLORS.foreground.clone().unwrap()
); cursor.style = Some(Arc::new(Style::new(NONE_COLORS)));
assert_eq!(
cursor.style = Some(Arc::new(Style::new(NONE_COLORS))); cursor.foreground(&DEFAULT_COLORS),
assert_eq!( DEFAULT_COLORS.background.clone().unwrap()
cursor.foreground(&DEFAULT_COLORS), );
DEFAULT_COLORS.background.clone().unwrap() }
);
} #[test]
fn test_background() {
#[test] let mut cursor = Cursor::new();
fn test_background() { let style = Some(Arc::new(Style::new(COLORS)));
let mut cursor = Cursor::new();
let style = Some(Arc::new(Style::new(COLORS))); assert_eq!(
cursor.background(&DEFAULT_COLORS),
assert_eq!( DEFAULT_COLORS.foreground.clone().unwrap()
cursor.background(&DEFAULT_COLORS), );
DEFAULT_COLORS.foreground.clone().unwrap() cursor.style = style.clone();
); assert_eq!(
cursor.style = style.clone(); cursor.background(&DEFAULT_COLORS),
assert_eq!( COLORS.background.clone().unwrap()
cursor.background(&DEFAULT_COLORS), );
COLORS.background.clone().unwrap()
); cursor.style = Some(Arc::new(Style::new(NONE_COLORS)));
assert_eq!(
cursor.style = Some(Arc::new(Style::new(NONE_COLORS))); cursor.background(&DEFAULT_COLORS),
assert_eq!( DEFAULT_COLORS.foreground.clone().unwrap()
cursor.background(&DEFAULT_COLORS), );
DEFAULT_COLORS.foreground.clone().unwrap() }
);
} #[test]
fn test_change_mode() {
#[test] let cursor_mode = CursorMode {
fn test_change_mode() { shape: Some(CursorShape::Horizontal),
let cursor_mode = CursorMode { style_id: Some(1),
shape: Some(CursorShape::Horizontal), cell_percentage: Some(100.0),
style_id: Some(1), blinkwait: Some(1),
cell_percentage: Some(100.0), blinkon: Some(1),
blinkwait: Some(1), blinkoff: Some(1),
blinkon: Some(1), };
blinkoff: Some(1), let mut styles = HashMap::new();
}; styles.insert(1, Arc::new(Style::new(COLORS)));
let mut styles = HashMap::new();
styles.insert(1, Arc::new(Style::new(COLORS))); let mut cursor = Cursor::new();
let mut cursor = Cursor::new(); cursor.change_mode(&cursor_mode, &styles);
assert_eq!(cursor.shape, CursorShape::Horizontal);
cursor.change_mode(&cursor_mode, &styles); assert_eq!(cursor.style, styles.get(&1).cloned());
assert_eq!(cursor.shape, CursorShape::Horizontal); assert_eq!(cursor.cell_percentage, Some(100.0));
assert_eq!(cursor.style, styles.get(&1).cloned()); assert_eq!(cursor.blinkwait, Some(1));
assert_eq!(cursor.cell_percentage, Some(100.0)); assert_eq!(cursor.blinkon, Some(1));
assert_eq!(cursor.blinkwait, Some(1)); assert_eq!(cursor.blinkoff, Some(1));
assert_eq!(cursor.blinkon, Some(1));
assert_eq!(cursor.blinkoff, Some(1)); let cursor_mode_with_none = CursorMode {
shape: None,
let cursor_mode_with_none = CursorMode { style_id: None,
shape: None, cell_percentage: None,
style_id: None, blinkwait: None,
cell_percentage: None, blinkon: None,
blinkwait: None, blinkoff: None,
blinkon: None, };
blinkoff: None, cursor.change_mode(&cursor_mode_with_none, &styles);
}; assert_eq!(cursor.shape, CursorShape::Horizontal);
cursor.change_mode(&cursor_mode_with_none, &styles); assert_eq!(cursor.style, styles.get(&1).cloned());
assert_eq!(cursor.shape, CursorShape::Horizontal); assert_eq!(cursor.cell_percentage, None);
assert_eq!(cursor.style, styles.get(&1).cloned()); assert_eq!(cursor.blinkwait, None);
assert_eq!(cursor.cell_percentage, None); assert_eq!(cursor.blinkon, None);
assert_eq!(cursor.blinkwait, None); assert_eq!(cursor.blinkoff, None);
assert_eq!(cursor.blinkon, None); }
assert_eq!(cursor.blinkoff, None); }
}
}

@ -54,6 +54,7 @@ pub enum DrawCommand {
UpdateCursor(Cursor), UpdateCursor(Cursor),
FontChanged(String), FontChanged(String),
DefaultStyleChanged(Style), DefaultStyleChanged(Style),
ModeChanged(EditorMode),
} }
pub enum WindowCommand { pub enum WindowCommand {
@ -71,6 +72,7 @@ impl fmt::Debug for DrawCommand {
DrawCommand::UpdateCursor(_) => write!(formatter, "UpdateCursor"), DrawCommand::UpdateCursor(_) => write!(formatter, "UpdateCursor"),
DrawCommand::FontChanged(_) => write!(formatter, "FontChanged"), DrawCommand::FontChanged(_) => write!(formatter, "FontChanged"),
DrawCommand::DefaultStyleChanged(_) => write!(formatter, "DefaultStyleChanged"), DrawCommand::DefaultStyleChanged(_) => write!(formatter, "DefaultStyleChanged"),
DrawCommand::ModeChanged(_) => write!(formatter, "ModeChanged"),
} }
} }
} }
@ -80,7 +82,6 @@ pub struct Editor {
pub cursor: Cursor, pub cursor: Cursor,
pub defined_styles: HashMap<u64, Arc<Style>>, pub defined_styles: HashMap<u64, Arc<Style>>,
pub mode_list: Vec<CursorMode>, pub mode_list: Vec<CursorMode>,
pub current_mode: EditorMode,
pub draw_command_batcher: Arc<DrawCommandBatcher>, pub draw_command_batcher: Arc<DrawCommandBatcher>,
pub window_command_sender: Sender<WindowCommand>, pub window_command_sender: Sender<WindowCommand>,
} }
@ -95,7 +96,6 @@ impl Editor {
cursor: Cursor::new(), cursor: Cursor::new(),
defined_styles: HashMap::new(), defined_styles: HashMap::new(),
mode_list: Vec::new(), mode_list: Vec::new(),
current_mode: EditorMode::Unknown(String::from("")),
draw_command_batcher: Arc::new(DrawCommandBatcher::new(batched_draw_command_sender)), draw_command_batcher: Arc::new(DrawCommandBatcher::new(batched_draw_command_sender)),
window_command_sender, window_command_sender,
} }
@ -113,8 +113,10 @@ impl Editor {
RedrawEvent::ModeChange { mode, mode_index } => { RedrawEvent::ModeChange { mode, mode_index } => {
if let Some(cursor_mode) = self.mode_list.get(mode_index as usize) { if let Some(cursor_mode) = self.mode_list.get(mode_index as usize) {
self.cursor.change_mode(cursor_mode, &self.defined_styles); self.cursor.change_mode(cursor_mode, &self.defined_styles);
self.current_mode = mode;
} }
self.draw_command_batcher
.queue(DrawCommand::ModeChanged(mode))
.ok();
} }
RedrawEvent::MouseOn => { RedrawEvent::MouseOn => {
self.window_command_sender self.window_command_sender
@ -218,6 +220,12 @@ impl Editor {
RedrawEvent::MessageSetPosition { grid, row, .. } => { RedrawEvent::MessageSetPosition { grid, row, .. } => {
self.set_message_position(grid, row) self.set_message_position(grid, row)
} }
RedrawEvent::WindowViewport {
grid,
top_line,
bottom_line,
..
} => self.send_updated_viewport(grid, top_line, bottom_line),
_ => {} _ => {}
}; };
} }
@ -385,23 +393,13 @@ impl Editor {
fn send_cursor_info(&mut self) { fn send_cursor_info(&mut self) {
let (grid_left, grid_top) = self.cursor.grid_position; let (grid_left, grid_top) = self.cursor.grid_position;
match self.get_window_top_left(self.cursor.parent_window_id) { if let Some(window) = self.windows.get(&self.cursor.parent_window_id) {
Some((window_left, window_top)) => { let (character, double_width) = window.get_cursor_character(grid_left, grid_top);
self.cursor.position = self.cursor.character = character;
(window_left + grid_left as f64, window_top + grid_top as f64); self.cursor.double_width = double_width;
} else {
if let Some(window) = self.windows.get(&self.cursor.parent_window_id) { self.cursor.double_width = false;
let (character, double_width) = self.cursor.character = " ".to_string();
window.get_cursor_character(grid_left, grid_top);
self.cursor.character = character;
self.cursor.double_width = double_width;
}
}
None => {
self.cursor.position = (grid_left as f64, grid_top as f64);
self.cursor.double_width = false;
self.cursor.character = " ".to_string();
}
} }
self.draw_command_batcher self.draw_command_batcher
.queue(DrawCommand::UpdateCursor(self.cursor.clone())) .queue(DrawCommand::UpdateCursor(self.cursor.clone()))
@ -419,6 +417,14 @@ impl Editor {
} }
} }
} }
fn send_updated_viewport(&mut self, grid: u64, top_line: f64, bottom_line: f64) {
if let Some(window) = self.windows.get_mut(&grid) {
window.update_viewport(top_line, bottom_line);
} else {
warn!("viewport event received before window initialized");
}
}
} }
pub fn start_editor( pub fn start_editor(

@ -38,6 +38,10 @@ pub enum WindowDrawCommand {
Show, Show,
Hide, Hide,
Close, Close,
Viewport {
top_line: f64,
bottom_line: f64,
},
} }
impl fmt::Debug for WindowDrawCommand { impl fmt::Debug for WindowDrawCommand {
@ -58,6 +62,14 @@ impl fmt::Debug for WindowDrawCommand {
WindowDrawCommand::Show => write!(formatter, "Show"), WindowDrawCommand::Show => write!(formatter, "Show"),
WindowDrawCommand::Hide => write!(formatter, "Hide"), WindowDrawCommand::Hide => write!(formatter, "Hide"),
WindowDrawCommand::Close => write!(formatter, "Close"), WindowDrawCommand::Close => write!(formatter, "Close"),
WindowDrawCommand::Viewport {
top_line,
bottom_line,
} => write!(
formatter,
"Viewport {{ top: {}, bottom: {} }}",
top_line, bottom_line
),
} }
} }
} }
@ -375,4 +387,11 @@ impl Window {
pub fn close(&self) { pub fn close(&self) {
self.send_command(WindowDrawCommand::Close); self.send_command(WindowDrawCommand::Close);
} }
pub fn update_viewport(&self, top_line: f64, bottom_line: f64) {
self.send_command(WindowDrawCommand::Viewport {
top_line,
bottom_line,
});
}
} }

@ -1,8 +1,12 @@
mod blink; mod blink;
mod cursor_vfx; mod cursor_vfx;
use std::collections::HashMap;
use skulpin::skia_safe::{Canvas, Paint, Path, Point}; use skulpin::skia_safe::{Canvas, Paint, Path, Point};
use super::RenderedWindow;
use crate::bridge::EditorMode;
use crate::editor::{Colors, Cursor, CursorShape}; use crate::editor::{Colors, Cursor, CursorShape};
use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::renderer::animation_utils::*; use crate::renderer::animation_utils::*;
@ -178,6 +182,7 @@ impl Corner {
pub struct CursorRenderer { pub struct CursorRenderer {
pub corners: Vec<Corner>, pub corners: Vec<Corner>,
cursor: Cursor, cursor: Cursor,
destination: Point,
blink_status: BlinkStatus, blink_status: BlinkStatus,
previous_cursor_shape: Option<CursorShape>, previous_cursor_shape: Option<CursorShape>,
cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>, cursor_vfx: Option<Box<dyn cursor_vfx::CursorVfx>>,
@ -189,9 +194,9 @@ impl CursorRenderer {
let mut renderer = CursorRenderer { let mut renderer = CursorRenderer {
corners: vec![Corner::new(); 4], corners: vec![Corner::new(); 4],
cursor: Cursor::new(), cursor: Cursor::new(),
destination: (0.0, 0.0).into(),
blink_status: BlinkStatus::new(), blink_status: BlinkStatus::new(),
previous_cursor_shape: None, previous_cursor_shape: None,
//cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)),
cursor_vfx: None, cursor_vfx: None,
previous_vfx_mode: cursor_vfx::VfxMode::Disabled, previous_vfx_mode: cursor_vfx::VfxMode::Disabled,
}; };
@ -233,10 +238,36 @@ impl CursorRenderer {
.collect::<Vec<Corner>>(); .collect::<Vec<Corner>>();
} }
pub fn update_cursor_destination(
&mut self,
font_width: f32,
font_height: f32,
windows: &HashMap<u64, RenderedWindow>,
) {
let (cursor_grid_x, cursor_grid_y) = self.cursor.grid_position;
if let Some(window) = windows.get(&self.cursor.parent_window_id) {
self.destination = (
(cursor_grid_x as f32 + window.grid_current_position.x) * font_width,
(cursor_grid_y as f32 + window.grid_current_position.y
- (window.current_scroll - window.current_surfaces.top_line))
* font_height,
)
.into();
} else {
self.destination = (
cursor_grid_x as f32 * font_width,
cursor_grid_y as f32 * font_height,
)
.into();
}
}
pub fn draw( pub fn draw(
&mut self, &mut self,
default_colors: &Colors, default_colors: &Colors,
font_size: (f32, f32), font_size: (f32, f32),
current_mode: &EditorMode,
shaper: &mut CachingShaper, shaper: &mut CachingShaper,
canvas: &mut Canvas, canvas: &mut Canvas,
dt: f32, dt: f32,
@ -253,7 +284,6 @@ impl CursorRenderer {
let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None); let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None);
paint.set_anti_alias(settings.antialiasing); paint.set_anti_alias(settings.antialiasing);
let (grid_x, grid_y) = self.cursor.position;
let character = self.cursor.character.clone(); let character = self.cursor.character.clone();
let font_width = match (self.cursor.double_width, &self.cursor.shape) { let font_width = match (self.cursor.double_width, &self.cursor.shape) {
@ -263,14 +293,9 @@ impl CursorRenderer {
let font_dimensions: Point = (font_width, font_height).into(); let font_dimensions: Point = (font_width, font_height).into();
let in_insert_mode = false; let in_insert_mode = matches!(current_mode, EditorMode::Insert);
// {
// 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 = self.destination + font_dimensions * 0.5;
let center_destination = destination + font_dimensions * 0.5;
let new_cursor = Some(self.cursor.shape.clone()); let new_cursor = Some(self.cursor.shape.clone());
if self.previous_cursor_shape != new_cursor { if self.previous_cursor_shape != new_cursor {
@ -340,7 +365,7 @@ impl CursorRenderer {
let blobs = &shaper.shape_cached(&character, false, false); let blobs = &shaper.shape_cached(&character, false, false);
for blob in blobs.iter() { for blob in blobs.iter() {
canvas.draw_text_blob(&blob, destination, &paint); canvas.draw_text_blob(&blob, self.destination, &paint);
} }
canvas.restore(); canvas.restore();

@ -1,335 +1,368 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::sync::Arc; use std::sync::Arc;
use log::{error, trace, warn}; use log::{error, trace, warn};
use skulpin::skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect}; use skulpin::skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect};
use skulpin::CoordinateSystemHelper; use skulpin::CoordinateSystemHelper;
pub mod animation_utils; pub mod animation_utils;
mod caching_shaper; mod caching_shaper;
pub mod cursor_renderer; pub mod cursor_renderer;
pub mod font_options; pub mod font_options;
mod rendered_window; mod rendered_window;
pub use caching_shaper::CachingShaper; pub use caching_shaper::CachingShaper;
pub use font_options::*; pub use font_options::*;
pub use rendered_window::{RenderedWindow, WindowDrawDetails}; pub use rendered_window::{RenderedWindow, WindowDrawDetails};
use crate::editor::{Colors, DrawCommand, Style, WindowDrawCommand}; use crate::bridge::EditorMode;
use crate::settings::*; use crate::editor::{Colors, DrawCommand, Style, WindowDrawCommand};
use cursor_renderer::CursorRenderer; use crate::settings::*;
use cursor_renderer::CursorRenderer;
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#[derive(Clone)]
pub struct RendererSettings { #[derive(Clone)]
animation_length: f32, pub struct RendererSettings {
floating_opacity: f32, position_animation_length: f32,
floating_blur: bool, scroll_animation_length: f32,
} floating_opacity: f32,
floating_blur: bool,
pub fn initialize_settings() { }
SETTINGS.set(&RendererSettings {
animation_length: 0.15, pub fn initialize_settings() {
floating_opacity: 0.7, SETTINGS.set(&RendererSettings {
floating_blur: true, position_animation_length: 0.15,
}); scroll_animation_length: 0.3,
floating_opacity: 0.7,
register_nvim_setting!( floating_blur: true,
"window_animation_length", });
RendererSettings::animation_length
); register_nvim_setting!(
register_nvim_setting!( "window_position_animation_length",
"floating_window_opacity", RendererSettings::position_animation_length
RendererSettings::floating_opacity );
); register_nvim_setting!(
register_nvim_setting!("floating_window_blur", RendererSettings::floating_blur); "window_scroll_animation_length",
} RendererSettings::scroll_animation_length
);
// ---------------------------------------------------------------------------- register_nvim_setting!(
"floating_window_opacity",
pub struct Renderer { RendererSettings::floating_opacity
rendered_windows: HashMap<u64, RenderedWindow>, );
cursor_renderer: CursorRenderer, register_nvim_setting!("floating_window_blur", RendererSettings::floating_blur);
}
pub paint: Paint,
pub shaper: CachingShaper, // ----------------------------------------------------------------------------
pub default_style: Arc<Style>,
pub font_width: f32, pub struct Renderer {
pub font_height: f32, rendered_windows: HashMap<u64, RenderedWindow>,
pub window_regions: Vec<WindowDrawDetails>, cursor_renderer: CursorRenderer,
pub batched_draw_command_receiver: Receiver<Vec<DrawCommand>>,
} pub current_mode: EditorMode,
pub paint: Paint,
impl Renderer { pub shaper: CachingShaper,
pub fn new(batched_draw_command_receiver: Receiver<Vec<DrawCommand>>) -> Renderer { pub default_style: Arc<Style>,
let rendered_windows = HashMap::new(); pub font_width: f32,
let cursor_renderer = CursorRenderer::new(); pub font_height: f32,
pub window_regions: Vec<WindowDrawDetails>,
let mut paint = Paint::new(colors::WHITE, None); pub batched_draw_command_receiver: Receiver<Vec<DrawCommand>>,
paint.set_anti_alias(false); }
let mut shaper = CachingShaper::new();
let (font_width, font_height) = shaper.font_base_dimensions(); impl Renderer {
let default_style = Arc::new(Style::new(Colors::new( pub fn new(batched_draw_command_receiver: Receiver<Vec<DrawCommand>>) -> Renderer {
Some(colors::WHITE), let rendered_windows = HashMap::new();
Some(colors::BLACK), let cursor_renderer = CursorRenderer::new();
Some(colors::GREY),
))); let current_mode = EditorMode::Unknown(String::from(""));
let window_regions = Vec::new(); let mut paint = Paint::new(colors::WHITE, None);
paint.set_anti_alias(false);
Renderer { let mut shaper = CachingShaper::new();
rendered_windows, let (font_width, font_height) = shaper.font_base_dimensions();
cursor_renderer, let default_style = Arc::new(Style::new(Colors::new(
Some(colors::WHITE),
paint, Some(colors::BLACK),
shaper, Some(colors::GREY),
default_style, )));
font_width, let window_regions = Vec::new();
font_height,
window_regions, Renderer {
batched_draw_command_receiver, rendered_windows,
} cursor_renderer,
}
current_mode,
fn update_font(&mut self, guifont_setting: &str) -> bool { paint,
let updated = self.shaper.update_font(guifont_setting); shaper,
if updated { default_style,
let (font_width, font_height) = self.shaper.font_base_dimensions(); font_width,
self.font_width = font_width; font_height,
self.font_height = font_height.ceil(); window_regions,
} batched_draw_command_receiver,
updated }
} }
fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect { fn update_font(&mut self, guifont_setting: &str) {
let (grid_x, grid_y) = grid_pos; if self.shaper.update_font(guifont_setting) {
let x = grid_x as f32 * self.font_width; let (font_width, font_height) = self.shaper.font_base_dimensions();
let y = grid_y as f32 * self.font_height; self.font_width = font_width;
let width = cell_width as f32 * self.font_width as f32; self.font_height = font_height.ceil();
let height = self.font_height as f32; }
Rect::new(x, y, x + width, y + height) }
}
fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect {
fn draw_background( let (grid_x, grid_y) = grid_pos;
&mut self, let x = grid_x as f32 * self.font_width;
canvas: &mut Canvas, let y = grid_y as f32 * self.font_height;
grid_pos: (u64, u64), let width = cell_width as f32 * self.font_width as f32;
cell_width: u64, let height = self.font_height as f32;
style: &Option<Arc<Style>>, Rect::new(x, y, x + width, y + height)
) { }
self.paint.set_blend_mode(BlendMode::Src);
fn get_default_background(&self) -> Color {
let region = self.compute_text_region(grid_pos, cell_width); self.default_style
let style = style.as_ref().unwrap_or(&self.default_style); .colors
.background
self.paint .clone()
.set_color(style.background(&self.default_style.colors).to_color()); .unwrap()
canvas.draw_rect(region, &self.paint); .to_color()
} }
fn draw_foreground( fn draw_background(
&mut self, &mut self,
canvas: &mut Canvas, canvas: &mut Canvas,
text: &str, grid_pos: (u64, u64),
grid_pos: (u64, u64), cell_width: u64,
cell_width: u64, style: &Option<Arc<Style>>,
style: &Option<Arc<Style>>, ) {
) { self.paint.set_blend_mode(BlendMode::Src);
let (grid_x, grid_y) = grid_pos;
let x = grid_x as f32 * self.font_width; let region = self.compute_text_region(grid_pos, cell_width);
let y = grid_y as f32 * self.font_height; let style = style.as_ref().unwrap_or(&self.default_style);
let width = cell_width as f32 * self.font_width;
self.paint
let style = style.as_ref().unwrap_or(&self.default_style); .set_color(style.background(&self.default_style.colors).to_color());
canvas.draw_rect(region, &self.paint);
canvas.save(); }
let region = self.compute_text_region(grid_pos, cell_width); fn draw_foreground(
&mut self,
canvas.clip_rect(region, None, Some(false)); canvas: &mut Canvas,
text: &str,
self.paint.set_blend_mode(BlendMode::Src); grid_pos: (u64, u64),
let transparent = Color::from_argb(0, 0, 0, 0); cell_width: u64,
self.paint.set_color(transparent); style: &Option<Arc<Style>>,
canvas.draw_rect(region, &self.paint); ) {
let (grid_x, grid_y) = grid_pos;
if style.underline || style.undercurl { let x = grid_x as f32 * self.font_width;
let line_position = self.shaper.underline_position(); let y = grid_y as f32 * self.font_height;
let stroke_width = self.shaper.options.size / 10.0; let width = cell_width as f32 * self.font_width;
self.paint
.set_color(style.special(&self.default_style.colors).to_color()); let style = style.as_ref().unwrap_or(&self.default_style);
self.paint.set_stroke_width(stroke_width);
canvas.save();
if style.undercurl {
self.paint.set_path_effect(dash_path_effect::new( let region = self.compute_text_region(grid_pos, cell_width);
&[stroke_width * 2.0, stroke_width * 2.0],
0.0, canvas.clip_rect(region, None, Some(false));
));
} else { self.paint.set_blend_mode(BlendMode::Src);
self.paint.set_path_effect(None); let transparent = Color::from_argb(0, 0, 0, 0);
} self.paint.set_color(transparent);
canvas.draw_rect(region, &self.paint);
canvas.draw_line(
(x, y - line_position + self.font_height), if style.underline || style.undercurl {
(x + width, y - line_position + self.font_height), let line_position = self.shaper.underline_position();
&self.paint, 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);
self.paint
.set_color(style.foreground(&self.default_style.colors).to_color()); if style.undercurl {
let text = text.trim_end(); self.paint.set_path_effect(dash_path_effect::new(
if !text.is_empty() { &[stroke_width * 2.0, stroke_width * 2.0],
for blob in self 0.0,
.shaper ));
.shape_cached(text, style.bold, style.italic) } else {
.iter() self.paint.set_path_effect(None);
{ }
canvas.draw_text_blob(blob, (x, y), &self.paint);
} canvas.draw_line(
} (x, y - line_position + self.font_height),
(x + width, y - line_position + self.font_height),
if style.strikethrough { &self.paint,
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); self.paint
} .set_color(style.foreground(&self.default_style.colors).to_color());
let text = text.trim_end();
canvas.restore(); if !text.is_empty() {
} for blob in self
.shaper
pub fn handle_draw_command(&mut self, root_canvas: &mut Canvas, draw_command: DrawCommand) { .shape_cached(text, style.bold, style.italic)
warn!("{:?}", &draw_command); .iter()
match draw_command { {
DrawCommand::Window { canvas.draw_text_blob(blob, (x, y), &self.paint);
grid_id, }
command: WindowDrawCommand::Close, }
} => {
self.rendered_windows.remove(&grid_id); if style.strikethrough {
} let line_position = region.center_y();
DrawCommand::Window { grid_id, command } => { self.paint
if let Some(rendered_window) = self.rendered_windows.remove(&grid_id) { .set_color(style.special(&self.default_style.colors).to_color());
warn!("Window positioned {}", grid_id); canvas.draw_line((x, line_position), (x + width, line_position), &self.paint);
let rendered_window = rendered_window.handle_window_draw_command(self, command); }
self.rendered_windows.insert(grid_id, rendered_window);
} else if let WindowDrawCommand::Position { canvas.restore();
grid_left, }
grid_top,
width, pub fn handle_draw_command(&mut self, root_canvas: &mut Canvas, draw_command: DrawCommand) {
height, warn!("{:?}", &draw_command);
.. match draw_command {
} = command DrawCommand::Window {
{ grid_id,
warn!("Created window {}", grid_id); command: WindowDrawCommand::Close,
let new_window = RenderedWindow::new( } => {
root_canvas, self.rendered_windows.remove(&grid_id);
&self, }
grid_id, DrawCommand::Window { grid_id, command } => {
(grid_left as f32, grid_top as f32).into(), if let Some(rendered_window) = self.rendered_windows.remove(&grid_id) {
width, let rendered_window = rendered_window.handle_window_draw_command(self, command);
height, self.rendered_windows.insert(grid_id, rendered_window);
); } else if let WindowDrawCommand::Position {
self.rendered_windows.insert(grid_id, new_window); grid_left,
} else { grid_top,
error!("WindowDrawCommand sent for uninitialized grid {}", grid_id); width,
} height,
} ..
DrawCommand::UpdateCursor(new_cursor) => { } = command
self.cursor_renderer.update_cursor(new_cursor); {
} warn!("Created window {}", grid_id);
DrawCommand::FontChanged(new_font) => { let new_window = RenderedWindow::new(
if self.update_font(&new_font) { root_canvas,
// Resize all the grids &self,
} grid_id,
} (grid_left as f32, grid_top as f32).into(),
DrawCommand::DefaultStyleChanged(new_style) => { width,
self.default_style = Arc::new(new_style); height,
} );
_ => {} self.rendered_windows.insert(grid_id, new_window);
} } else {
} error!("WindowDrawCommand sent for uninitialized grid {}", grid_id);
}
pub fn draw_frame( }
&mut self, DrawCommand::UpdateCursor(new_cursor) => {
root_canvas: &mut Canvas, self.cursor_renderer.update_cursor(new_cursor);
coordinate_system_helper: &CoordinateSystemHelper, }
dt: f32, DrawCommand::FontChanged(new_font) => {
) -> bool { self.update_font(&new_font);
trace!("Rendering"); }
let mut font_changed = false; DrawCommand::DefaultStyleChanged(new_style) => {
self.default_style = Arc::new(new_style);
let draw_commands: Vec<DrawCommand> = self }
.batched_draw_command_receiver DrawCommand::ModeChanged(new_mode) => {
.try_iter() // Iterator of Vec of DrawCommand self.current_mode = new_mode;
.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; pub fn draw_frame(
} &mut self,
self.handle_draw_command(root_canvas, draw_command); root_canvas: &mut Canvas,
} coordinate_system_helper: &CoordinateSystemHelper,
dt: f32,
root_canvas.clear( ) -> bool {
self.default_style trace!("Rendering");
.colors let mut font_changed = false;
.background
.clone() let draw_commands: Vec<DrawCommand> = self
.unwrap() .batched_draw_command_receiver
.to_color(), .try_iter() // Iterator of Vec of DrawCommand
); .map(|batch| batch.into_iter()) // Iterator of Iterator of DrawCommand
.flatten() // Iterator of DrawCommand
root_canvas.save(); .collect(); // Vec of DrawCommand
for draw_command in draw_commands.into_iter() {
if let Some(root_window) = self.rendered_windows.get(&1) { if let DrawCommand::FontChanged(_) = draw_command {
let clip_rect = root_window.pixel_region(self.font_width, self.font_height); font_changed = true;
root_canvas.clip_rect(&clip_rect, None, Some(false)); }
} self.handle_draw_command(root_canvas, draw_command);
}
coordinate_system_helper.use_logical_coordinates(root_canvas);
root_canvas.clear(
let windows: Vec<&mut RenderedWindow> = { self.default_style
let (mut root_windows, mut floating_windows): ( .colors
Vec<&mut RenderedWindow>, .background
Vec<&mut RenderedWindow>, .clone()
) = self .unwrap()
.rendered_windows .to_color(),
.values_mut() );
.filter(|window| !window.hidden)
.partition(|window| !window.floating); root_canvas.save();
root_windows if let Some(root_window) = self.rendered_windows.get(&1) {
.sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap()); let clip_rect = root_window.pixel_region(self.font_width, self.font_height);
floating_windows root_canvas.clip_rect(&clip_rect, None, Some(false));
.sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap()); }
root_windows coordinate_system_helper.use_logical_coordinates(root_canvas);
.into_iter()
.chain(floating_windows.into_iter()) let default_background = self.get_default_background();
.collect() let font_width = self.font_width;
}; let font_height = self.font_height;
let settings = SETTINGS.get::<RendererSettings>(); let windows: Vec<&mut RenderedWindow> = {
let font_width = self.font_width; let (mut root_windows, mut floating_windows): (
let font_height = self.font_height; Vec<&mut RenderedWindow>,
self.window_regions = windows Vec<&mut RenderedWindow>,
.into_iter() ) = self
.map(|window| window.draw(root_canvas, &settings, font_width, font_height, dt)) .rendered_windows
.collect(); .values_mut()
.filter(|window| !window.hidden)
self.cursor_renderer.draw( .partition(|window| !window.floating);
&self.default_style.colors,
(self.font_width, self.font_height), root_windows
&mut self.shaper, .sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap());
root_canvas, floating_windows
dt, .sort_by(|window_a, window_b| window_a.id.partial_cmp(&window_b.id).unwrap());
);
root_windows
root_canvas.restore(); .into_iter()
.chain(floating_windows.into_iter())
font_changed .collect()
} };
}
let settings = SETTINGS.get::<RendererSettings>();
self.window_regions = windows
.into_iter()
.map(|window| {
window.draw(
root_canvas,
&settings,
default_background,
font_width,
font_height,
dt,
)
})
.collect();
let windows = &self.rendered_windows;
self.cursor_renderer
.update_cursor_destination(font_width, font_height, windows);
self.cursor_renderer.draw(
&self.default_style.colors,
(self.font_width, self.font_height),
&self.current_mode,
&mut self.shaper,
root_canvas,
dt,
);
root_canvas.restore();
font_changed
}
}

@ -1,3 +1,6 @@
use std::collections::VecDeque;
use std::iter;
use skulpin::skia_safe::canvas::{SaveLayerRec, SrcRectConstraint}; use skulpin::skia_safe::canvas::{SaveLayerRec, SrcRectConstraint};
use skulpin::skia_safe::gpu::SurfaceOrigin; use skulpin::skia_safe::gpu::SurfaceOrigin;
use skulpin::skia_safe::{ use skulpin::skia_safe::{
@ -11,14 +14,10 @@ use crate::redraw_scheduler::REDRAW_SCHEDULER;
fn build_window_surface( fn build_window_surface(
parent_canvas: &mut Canvas, parent_canvas: &mut Canvas,
renderer: &Renderer, pixel_width: i32,
grid_width: u64, pixel_height: i32,
grid_height: u64,
) -> Surface { ) -> Surface {
let dimensions = ( let dimensions = (pixel_width, pixel_height);
(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 mut context = parent_canvas.gpu_context().unwrap();
let budgeted = Budgeted::Yes; let budgeted = Budgeted::Yes;
let parent_image_info = parent_canvas.image_info(); let parent_image_info = parent_canvas.image_info();
@ -41,29 +40,84 @@ fn build_window_surface(
.expect("Could not create surface") .expect("Could not create surface")
} }
fn build_window_surface_with_grid_size(
parent_canvas: &mut Canvas,
renderer: &Renderer,
grid_width: u64,
grid_height: u64,
) -> Surface {
let pixel_width = (grid_width as f32 * renderer.font_width) as i32;
let pixel_height = (grid_height as f32 * renderer.font_height) as i32;
build_window_surface(parent_canvas, pixel_width, pixel_height)
}
fn build_background_window_surface( fn build_background_window_surface(
parent_canvas: &mut Canvas, parent_canvas: &mut Canvas,
renderer: &Renderer, renderer: &Renderer,
grid_width: u64, grid_width: u64,
grid_height: u64, grid_height: u64,
) -> Surface { ) -> Surface {
let mut surface = build_window_surface(parent_canvas, renderer, grid_width, grid_height); let mut surface =
build_window_surface_with_grid_size(parent_canvas, renderer, grid_width, grid_height);
let canvas = surface.canvas(); let canvas = surface.canvas();
canvas.clear( canvas.clear(renderer.get_default_background());
renderer
.default_style
.colors
.background
.clone()
.unwrap()
.to_color(),
);
surface surface
} }
fn clone_window_surface(surface: &mut Surface) -> Surface {
let snapshot = surface.image_snapshot();
let mut canvas = surface.canvas();
let mut new_surface = build_window_surface(&mut canvas, snapshot.width(), snapshot.height());
let new_canvas = new_surface.canvas();
new_canvas.draw_image(snapshot, (0.0, 0.0), None);
new_surface
}
pub struct SurfacePair {
background: Surface,
foreground: Surface,
pub top_line: f32,
bottom_line: f32,
}
impl SurfacePair {
fn new(
parent_canvas: &mut Canvas,
renderer: &Renderer,
grid_width: u64,
grid_height: u64,
top_line: f32,
bottom_line: f32,
) -> SurfacePair {
let background =
build_background_window_surface(parent_canvas, renderer, grid_width, grid_height);
let foreground =
build_window_surface_with_grid_size(parent_canvas, renderer, grid_width, grid_height);
SurfacePair {
background,
foreground,
top_line,
bottom_line,
}
}
fn clone(&mut self) -> SurfacePair {
let new_background = clone_window_surface(&mut self.background);
let new_foreground = clone_window_surface(&mut self.foreground);
SurfacePair {
background: new_background,
foreground: new_foreground,
top_line: self.top_line,
bottom_line: self.bottom_line,
}
}
}
pub struct RenderedWindow { pub struct RenderedWindow {
background_surface: Surface, old_surfaces: VecDeque<SurfacePair>,
foreground_surface: Surface, pub current_surfaces: SurfacePair,
pub id: u64, pub id: u64,
pub hidden: bool, pub hidden: bool,
pub floating: bool, pub floating: bool,
@ -72,9 +126,14 @@ pub struct RenderedWindow {
grid_height: u64, grid_height: u64,
grid_start_position: Point, grid_start_position: Point,
grid_current_position: Point, pub grid_current_position: Point,
grid_destination: Point, grid_destination: Point,
t: f32, position_t: f32,
start_scroll: f32,
pub current_scroll: f32,
scroll_destination: f32,
scroll_t: f32,
} }
pub struct WindowDrawDetails { pub struct WindowDrawDetails {
@ -92,14 +151,12 @@ impl RenderedWindow {
grid_width: u64, grid_width: u64,
grid_height: u64, grid_height: u64,
) -> RenderedWindow { ) -> RenderedWindow {
let background_surface = let current_surfaces =
build_background_window_surface(parent_canvas, renderer, grid_width, grid_height); SurfacePair::new(parent_canvas, renderer, grid_width, grid_height, 0.0, 0.0);
let foreground_surface =
build_window_surface(parent_canvas, renderer, grid_width, grid_height);
RenderedWindow { RenderedWindow {
background_surface, old_surfaces: VecDeque::new(),
foreground_surface, current_surfaces,
id, id,
hidden: false, hidden: false,
floating: false, floating: false,
@ -110,7 +167,12 @@ impl RenderedWindow {
grid_start_position: grid_position, grid_start_position: grid_position,
grid_current_position: grid_position, grid_current_position: grid_position,
grid_destination: grid_position, grid_destination: grid_position,
t: 2.0, // 2.0 is out of the 0.0 to 1.0 range and stops animation position_t: 2.0, // 2.0 is out of the 0.0 to 1.0 range and stops animation
start_scroll: 0.0,
current_scroll: 0.0,
scroll_destination: 0.0,
scroll_t: 2.0, // 2.0 is out of the 0.0 to 1.0 range and stops animation
} }
} }
@ -127,31 +189,52 @@ impl RenderedWindow {
} }
pub fn update(&mut self, settings: &RendererSettings, dt: f32) -> bool { pub fn update(&mut self, settings: &RendererSettings, dt: f32) -> bool {
if (self.t - 1.0).abs() < std::f32::EPSILON { let mut animating = false;
return false;
} {
if (self.position_t - 1.0).abs() < std::f32::EPSILON {
// We are at destination, move t out of 0-1 range to stop the animation
self.position_t = 2.0;
} else {
animating = true;
self.position_t =
(self.position_t + dt / settings.position_animation_length).min(1.0);
}
if (self.t - 1.0).abs() < std::f32::EPSILON { self.grid_current_position = ease_point(
// We are at destination, move t out of 0-1 range to stop the animation ease_out_expo,
self.t = 2.0; self.grid_start_position,
} else { self.grid_destination,
self.t = (self.t + dt / settings.animation_length).min(1.0); self.position_t,
);
} }
self.grid_current_position = ease_point( {
ease_out_expo, if (self.scroll_t - 1.0).abs() < std::f32::EPSILON {
self.grid_start_position, // We are at destination, move t out of 0-1 range to stop the animation
self.grid_destination, self.scroll_t = 2.0;
self.t, self.old_surfaces.clear();
); } else {
animating = true;
self.scroll_t = (self.scroll_t + dt / settings.scroll_animation_length).min(1.0);
}
self.current_scroll = ease(
ease_out_expo,
self.start_scroll,
self.scroll_destination,
self.scroll_t,
);
}
true animating
} }
pub fn draw( pub fn draw(
&mut self, &mut self,
root_canvas: &mut Canvas, root_canvas: &mut Canvas,
settings: &RendererSettings, settings: &RendererSettings,
default_background: Color,
font_width: f32, font_width: f32,
font_height: f32, font_height: f32,
dt: f32, dt: f32,
@ -163,7 +246,6 @@ impl RenderedWindow {
let pixel_region = self.pixel_region(font_width, font_height); let pixel_region = self.pixel_region(font_width, font_height);
root_canvas.save(); root_canvas.save();
root_canvas.clip_rect(&pixel_region, None, Some(false)); root_canvas.clip_rect(&pixel_region, None, Some(false));
if self.floating && settings.floating_blur { if self.floating && settings.floating_blur {
@ -176,26 +258,55 @@ impl RenderedWindow {
} }
let mut paint = Paint::default(); let mut paint = Paint::default();
// We want each surface to overwrite the one underneath and will use layers to ensure
// only lower priority surfaces will get clobbered and not the underlying windows
paint.set_blend_mode(BlendMode::Src);
{
// Save layer so that setting the blend mode doesn't effect the blur
root_canvas.save_layer(&SaveLayerRec::default());
let mut a = 255;
if self.floating {
a = (settings.floating_opacity.min(1.0).max(0.0) * 255.0) as u8;
}
if self.floating { paint.set_color(default_background.with_a(a));
let a = (settings.floating_opacity.min(1.0).max(0.0) * 255.0) as u8; root_canvas.draw_rect(pixel_region, &paint);
paint.set_color(Color::from_argb(a, 255, 255, 255));
}
self.background_surface.draw( paint.set_color(Color::from_argb(a, 255, 255, 255));
root_canvas.as_mut(),
(pixel_region.left(), pixel_region.top()),
Some(&paint),
);
let mut paint = Paint::default(); for surface_pair in iter::once(&mut self.current_surfaces)
paint.set_blend_mode(BlendMode::SrcOver); .chain(self.old_surfaces.iter_mut())
.rev()
{
let scroll_offset =
surface_pair.top_line * font_height - self.current_scroll * font_height;
surface_pair.background.draw(
root_canvas.as_mut(),
(pixel_region.left(), pixel_region.top() + scroll_offset),
Some(&paint),
);
}
root_canvas.restore();
}
self.foreground_surface.draw( {
root_canvas.as_mut(), // Save layer so that text may safely overwrite images underneath
(pixel_region.left(), pixel_region.top()), root_canvas.save_layer(&SaveLayerRec::default());
Some(&paint), for surface_pair in iter::once(&mut self.current_surfaces)
); .chain(self.old_surfaces.iter_mut())
.rev()
{
let scroll_offset =
surface_pair.top_line * font_height - self.current_scroll * font_height;
surface_pair.foreground.draw(
root_canvas.as_mut(),
(pixel_region.left(), pixel_region.top() + scroll_offset),
Some(&paint),
);
}
root_canvas.restore();
}
if self.floating { if self.floating {
root_canvas.restore(); root_canvas.restore();
@ -229,13 +340,13 @@ impl RenderedWindow {
if self.grid_start_position.x.abs() > f32::EPSILON if self.grid_start_position.x.abs() > f32::EPSILON
|| self.grid_start_position.y.abs() > f32::EPSILON || self.grid_start_position.y.abs() > f32::EPSILON
{ {
self.t = 0.0; // Reset animation as we have a new destination. self.position_t = 0.0; // Reset animation as we have a new destination.
self.grid_start_position = self.grid_current_position; self.grid_start_position = self.grid_current_position;
self.grid_destination = new_destination; self.grid_destination = new_destination;
} else { } else {
// We don't want to animate since the window is animating out of the start location, // We don't want to animate since the window is animating out of the start location,
// so we set t to 2.0 to stop animations. // so we set t to 2.0 to stop animations.
self.t = 2.0; self.position_t = 2.0;
self.grid_start_position = new_destination; self.grid_start_position = new_destination;
self.grid_destination = new_destination; self.grid_destination = new_destination;
} }
@ -243,25 +354,33 @@ impl RenderedWindow {
if grid_width != self.grid_width || grid_height != self.grid_height { if grid_width != self.grid_width || grid_height != self.grid_height {
{ {
let mut old_background = self.background_surface; let mut old_background = self.current_surfaces.background;
self.background_surface = build_background_window_surface( self.current_surfaces.background = build_background_window_surface(
old_background.canvas(), old_background.canvas(),
&renderer, &renderer,
grid_width, grid_width,
grid_height, grid_height,
); );
old_background.draw(self.background_surface.canvas(), (0.0, 0.0), None); old_background.draw(
self.current_surfaces.background.canvas(),
(0.0, 0.0),
None,
);
} }
{ {
let mut old_foreground = self.foreground_surface; let mut old_foreground = self.current_surfaces.foreground;
self.foreground_surface = build_window_surface( self.current_surfaces.foreground = build_window_surface_with_grid_size(
old_foreground.canvas(), old_foreground.canvas(),
&renderer, &renderer,
grid_width, grid_width,
grid_height, grid_height,
); );
old_foreground.draw(self.foreground_surface.canvas(), (0.0, 0.0), None); old_foreground.draw(
self.current_surfaces.foreground.canvas(),
(0.0, 0.0),
None,
);
} }
self.grid_width = grid_width; self.grid_width = grid_width;
@ -272,7 +391,7 @@ impl RenderedWindow {
if self.hidden { if self.hidden {
self.hidden = false; self.hidden = false;
self.t = 2.0; // We don't want to animate since the window is becoming visible, so we set t to 2.0 to stop animations. self.position_t = 2.0; // We don't want to animate since the window is becoming visible, so we set t to 2.0 to stop animations.
self.grid_start_position = new_destination; self.grid_start_position = new_destination;
self.grid_destination = new_destination; self.grid_destination = new_destination;
} }
@ -287,7 +406,7 @@ impl RenderedWindow {
let grid_position = (window_left, window_top); let grid_position = (window_left, window_top);
{ {
let mut background_canvas = self.background_surface.canvas(); let mut background_canvas = self.current_surfaces.background.canvas();
renderer.draw_background( renderer.draw_background(
&mut background_canvas, &mut background_canvas,
grid_position, grid_position,
@ -297,7 +416,7 @@ impl RenderedWindow {
} }
{ {
let mut foreground_canvas = self.foreground_surface.canvas(); let mut foreground_canvas = self.current_surfaces.foreground.canvas();
renderer.draw_foreground( renderer.draw_foreground(
&mut foreground_canvas, &mut foreground_canvas,
&text, &text,
@ -323,8 +442,8 @@ impl RenderedWindow {
); );
{ {
let background_snapshot = self.background_surface.image_snapshot(); let background_snapshot = self.current_surfaces.background.image_snapshot();
let background_canvas = self.background_surface.canvas(); let background_canvas = self.current_surfaces.background.canvas();
background_canvas.save(); background_canvas.save();
background_canvas.clip_rect(scrolled_region, None, Some(false)); background_canvas.clip_rect(scrolled_region, None, Some(false));
@ -346,8 +465,8 @@ impl RenderedWindow {
} }
{ {
let foreground_snapshot = self.foreground_surface.image_snapshot(); let foreground_snapshot = self.current_surfaces.foreground.image_snapshot();
let foreground_canvas = self.foreground_surface.canvas(); let foreground_canvas = self.current_surfaces.foreground.canvas();
foreground_canvas.save(); foreground_canvas.save();
foreground_canvas.clip_rect(scrolled_region, None, Some(false)); foreground_canvas.clip_rect(scrolled_region, None, Some(false));
@ -369,16 +488,16 @@ impl RenderedWindow {
} }
} }
WindowDrawCommand::Clear => { WindowDrawCommand::Clear => {
let background_canvas = self.background_surface.canvas(); let background_canvas = self.current_surfaces.background.canvas();
self.background_surface = build_background_window_surface( self.current_surfaces.background = build_background_window_surface(
background_canvas, background_canvas,
&renderer, &renderer,
self.grid_width, self.grid_width,
self.grid_height, self.grid_height,
); );
let foreground_canvas = self.foreground_surface.canvas(); let foreground_canvas = self.current_surfaces.foreground.canvas();
self.foreground_surface = build_window_surface( self.current_surfaces.foreground = build_window_surface_with_grid_size(
foreground_canvas, foreground_canvas,
&renderer, &renderer,
self.grid_width, self.grid_width,
@ -388,11 +507,34 @@ impl RenderedWindow {
WindowDrawCommand::Show => { WindowDrawCommand::Show => {
if self.hidden { if self.hidden {
self.hidden = false; self.hidden = false;
self.t = 2.0; // We don't want to animate since the window is becoming visible, so we set t to 2.0 to stop animations. self.position_t = 2.0; // We don't want to animate since the window is becoming visible, so we set t to 2.0 to stop animations.
self.grid_start_position = self.grid_destination; self.grid_start_position = self.grid_destination;
} }
} }
WindowDrawCommand::Hide => self.hidden = true, WindowDrawCommand::Hide => self.hidden = true,
WindowDrawCommand::Viewport {
top_line,
bottom_line,
} => {
if (self.current_surfaces.top_line - top_line as f32).abs() > std::f32::EPSILON {
let mut new_surfaces = self.current_surfaces.clone();
new_surfaces.top_line = top_line as f32;
new_surfaces.bottom_line = bottom_line as f32;
// Set new target viewport position and initialize animation timer
self.start_scroll = self.current_scroll;
self.scroll_destination = top_line as f32;
self.scroll_t = 0.0;
let current_surfaces = self.current_surfaces;
self.current_surfaces = new_surfaces;
self.old_surfaces.push_back(current_surfaces);
if self.old_surfaces.len() > 5 {
self.old_surfaces.pop_front();
}
}
}
_ => {} _ => {}
}; };

Loading…
Cancel
Save