From 337520768c4ef54439eeff69943ae126083b092c Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Tue, 6 Aug 2019 22:36:24 +0300 Subject: [PATCH] Improve gird resizes There were some problems with synchronizing grid resize events sent to nvim from various sources, e.g. set font, set linespace and window resize events. To solve this, set font and set linespace handling is now postponed till the next `flush` event which we'll eventually receive from nvim. The uiattach call to nvim could still be more efficient. Fix #82 Close #86 --- src/ui/font.rs | 2 +- src/ui/grid/context.rs | 70 ++++++++---------- src/ui/grid/grid.rs | 60 +++++++-------- src/ui/ui.rs | 164 ++++++++++++++++++++++------------------- 4 files changed, 147 insertions(+), 149 deletions(-) diff --git a/src/ui/font.rs b/src/ui/font.rs index a7336e10..6f3a1d74 100644 --- a/src/ui/font.rs +++ b/src/ui/font.rs @@ -17,7 +17,7 @@ impl Display for FontUnit { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Font { name: String, pub height: f32, diff --git a/src/ui/grid/context.rs b/src/ui/grid/context.rs index d2b9c5f9..932be803 100644 --- a/src/ui/grid/context.rs +++ b/src/ui/grid/context.rs @@ -1,12 +1,12 @@ use cairo; use gtk::DrawingArea; use pango; -use pango::FontDescription; use pangocairo; use gtk::prelude::*; use ui::color::{Color, Highlight}; +use ui::font::Font; use ui::grid::render; use ui::grid::row::Row; @@ -62,11 +62,12 @@ impl Context { let pango_context = pangocairo::functions::create_context(&cairo_context).unwrap(); - let font_desc = FontDescription::from_string("Monospace 12"); + let font = Font::from_guifont("Monospace:h12").unwrap(); + let font_desc = font.as_pango_font(); pango_context.set_font_description(&font_desc); let mut cell_metrics = CellMetrics::default(); - cell_metrics.font_desc = font_desc; + cell_metrics.font = font; cell_metrics.line_space = 0; cell_metrics.update(&pango_context); @@ -121,7 +122,7 @@ impl Context { self.cairo_context.restore(); let pctx = pangocairo::functions::create_context(&ctx).unwrap(); - pctx.set_font_description(&self.cell_metrics.font_desc); + pctx.set_font_description(&self.cell_metrics.font.as_pango_font()); self.cairo_context = ctx; self.pango_context = pctx; @@ -134,44 +135,29 @@ impl Context { /// make the update take place. pub fn update_metrics( &mut self, - mut font: Option, - mut line_space: Option, + font: Font, + line_space: i64, + da: >k::DrawingArea, ) { - if self.cell_metrics_update.is_none() { - self.cell_metrics_update = Some(self.cell_metrics.clone()); - } - - if let Some(ref mut cm) = self.cell_metrics_update { - if let Some(font) = font.take() { - cm.font_desc = font; - } - if let Some(line_space) = line_space.take() { - cm.line_space = line_space; - } + self.pango_context + .set_font_description(&font.as_pango_font()); - cm.update(&self.pango_context); - } - } + self.cell_metrics.font = font; + self.cell_metrics.line_space = line_space; + self.cell_metrics.update(&self.pango_context); - /// Finishes cell metrics update if one is on going. - pub fn finish_metrics_update(&mut self, da: &DrawingArea) { - if let Some(cm) = self.cell_metrics_update.take() { - self.pango_context.set_font_description(&cm.font_desc); - self.cell_metrics = cm; - - self.cursor_context = { - let win = da.get_window().unwrap(); - let surface = win - .create_similar_surface( - cairo::Content::ColorAlpha, - (self.cell_metrics.width * 2.0) as i32, // times two for double width chars. - self.cell_metrics.height as i32 - + self.cell_metrics.ascent as i32, - ) - .unwrap(); - cairo::Context::new(&surface) - }; - } + self.cursor_context = { + let win = da.get_window().unwrap(); + let surface = win + .create_similar_surface( + cairo::Content::ColorAlpha, + (self.cell_metrics.width * 2.0) as i32, // times two for double width chars. + self.cell_metrics.height as i32 + + self.cell_metrics.ascent as i32, + ) + .unwrap(); + cairo::Context::new(&surface) + }; } /// Returns x, y, width and height for current cursor location. @@ -215,12 +201,14 @@ pub struct CellMetrics { pub underline_position: f64, pub line_space: i64, - pub font_desc: FontDescription, + pub font: Font, } impl CellMetrics { pub fn update(&mut self, ctx: &pango::Context) { - let fm = ctx.get_metrics(Some(&self.font_desc), None).unwrap(); + let fm = ctx + .get_metrics(Some(&self.font.as_pango_font()), None) + .unwrap(); let extra = self.line_space as f64 / 2.0; self.ascent = fm.get_ascent() as f64 / pango::SCALE as f64 + extra; self.decent = fm.get_descent() as f64 / pango::SCALE as f64 + extra; diff --git a/src/ui/grid/grid.rs b/src/ui/grid/grid.rs index d8c2ef4a..0e545c0d 100644 --- a/src/ui/grid/grid.rs +++ b/src/ui/grid/grid.rs @@ -7,12 +7,12 @@ use gdk; use gdk::{EventMask, ModifierType}; use gtk; use gtk::{DrawingArea, EventBox}; -use pango::FontDescription; use gtk::prelude::*; use nvim_bridge::{GridLineSegment, ModeInfo}; use thread_guard::ThreadGuard; +use ui::font::Font; use ui::grid::context::Context; use ui::grid::render; use ui::grid::row::Row; @@ -348,11 +348,22 @@ impl Grid { } } - pub fn resize(&self, width: u64, height: u64) { + /// Calcualtes the current size of the grid. + pub fn calc_size(&self) -> (i64, i64) { let mut ctx = self.context.borrow_mut(); let ctx = ctx.as_mut().unwrap(); - ctx.finish_metrics_update(&self.da); + let w = self.da.get_allocated_width(); + let h = self.da.get_allocated_height(); + let cols = (w / ctx.cell_metrics.width as i32) as i64; + let rows = (h / ctx.cell_metrics.height as i32) as i64; + + (cols, rows) + } + + pub fn resize(&self, width: u64, height: u64) { + let mut ctx = self.context.borrow_mut(); + let ctx = ctx.as_mut().unwrap(); // Clear internal grid (rows). ctx.rows = vec![]; @@ -437,20 +448,26 @@ impl Grid { .queue_draw_area(x as i32, y as i32, w as i32, h as i32); } - /// Sets line space. Actual change is postponed till the next call - /// to `resize`. - pub fn set_line_space(&self, space: i64) { + /// Set a new font and line space. This will likely change the cell metrics. + /// Use `calc_size` to receive the updated size (cols and rows) of the grid. + pub fn update_cell_metrics(&self, font: Font, line_space: i64) { let mut ctx = self.context.borrow_mut(); let ctx = ctx.as_mut().unwrap(); - ctx.update_metrics(None, Some(space)); + ctx.update_metrics(font, line_space, &self.da); } - /// Sets font. Actual change is postponed till the next call - /// to `resize`. - pub fn set_font(&self, font: FontDescription) { - let mut ctx = self.context.borrow_mut(); - let ctx = ctx.as_mut().unwrap(); - ctx.update_metrics(Some(font), None); + /// Get the current line space value. + pub fn get_line_space(&self) -> i64 { + let ctx = self.context.borrow(); + let ctx = ctx.as_ref().unwrap(); + ctx.cell_metrics.line_space + } + + /// Get a copy of the current font. + pub fn get_font(&self) -> Font { + let ctx = self.context.borrow(); + let ctx = ctx.as_ref().unwrap(); + ctx.cell_metrics.font.clone() } pub fn set_mode(&self, mode: &ModeInfo) { @@ -467,23 +484,6 @@ impl Grid { ctx.busy = busy; } - - /// Calculates the current gird size. Returns (rows, cols). - pub fn calc_size_for_new_metrics(&self) -> Option<(usize, usize)> { - let ctx = self.context.borrow(); - let ctx = ctx.as_ref().unwrap(); - - if let Some(ref cm) = ctx.cell_metrics_update { - let w = self.da.get_allocated_width(); - let h = self.da.get_allocated_height(); - let cols = (w / cm.width as i32) as usize; - let rows = (h / cm.height as i32) as usize; - - Some((rows, cols)) - } else { - None - } - } } /// Handler for grid's drawingarea's draw event. Draws the internal cairo diff --git a/src/ui/ui.rs b/src/ui/ui.rs index 096aafea..74c44169 100644 --- a/src/ui/ui.rs +++ b/src/ui/ui.rs @@ -53,6 +53,11 @@ impl HlDefs { } } +struct ResizeOptions { + font: Font, + line_space: i64, +} + /// Internal structure for `UI` to work on. struct UIState { /// All grids currently in the UI. @@ -76,6 +81,8 @@ struct UIState { /// Source id for delayed call to ui_try_resize. resize_source_id: Arc>>, + /// Resize options that is some if a resize should be send to nvim on flush. + resize_on_flush: Option, } /// Main UI structure. @@ -135,7 +142,9 @@ impl UI { // When resizing our window (main grid), we'll have to tell neovim to // resize it self also. The notify to nvim is send with a small delay, // so we don't spam it multiple times a second. source_id is used to - // track the function timeout. + // track the function timeout. This timeout might be canceled in + // redraw even handler if we receive a message that changes the size + // of the main grid. let source_id = Arc::new(Mutex::new(None)); let source_id_ref = source_id.clone(); let nvim_ref = nvim.clone(); @@ -165,12 +174,9 @@ impl UI { let source_id = source_id_ref.clone(); let mut source_id = source_id.lock().unwrap(); - { - // If we have earlier timeout, remove it. - let old = source_id.take(); - if let Some(old) = old { - glib::source::source_remove(old); - } + // If we have earlier timeout, remove it. + if let Some(old) = source_id.take() { + glib::source::source_remove(old); } *source_id = Some(new); @@ -296,6 +302,7 @@ impl UI { cursor_tooltip, resize_source_id: source_id, hl_defs, + resize_on_flush: None, })), nvim, } @@ -600,76 +607,40 @@ fn handle_redraw_event( }); } RedrawEvent::OptionSet(evt) => { - evt.iter().for_each(|opt| { - match opt { - OptionSet::GuiFont(font) => { - let font = Font::from_guifont(font) - .unwrap_or(Font::default()); - let pango_font = font.as_pango_font(); - - for grid in (state.grids).values() { - grid.set_font(pango_font.clone()); - } - - // Cancel any possible delayed call for ui_try_resize. - let mut id = state.resize_source_id.lock().unwrap(); - if let Some(id) = id.take() { - glib::source::source_remove(id); - } - - // Channing the font affects the grid size, so we'll - // need to tell nvim our new size. - let grid = state.grids.get(&1).unwrap(); - let (rows, cols) = - grid.calc_size_for_new_metrics().unwrap(); - let mut nvim = nvim.lock().unwrap(); - nvim.ui_try_resize_async(cols as i64, rows as i64) - .cb(|res| { - if let Err(err) = res { - eprintln!("Error: failed to resize nvim on font change ({:?})", err); - } - }) - .call(); - - state - .popupmenu - .set_font(font.clone(), &state.hl_defs); - state - .cmdline - .set_font(font.clone(), &state.hl_defs); - state - .tabline - .set_font(font.clone(), &state.hl_defs); - state.cursor_tooltip.set_font(font.clone()); - } - OptionSet::LineSpace(val) => { - for grid in state.grids.values() { - grid.set_line_space(*val); - } - - // Channing the linespace affects the grid size, - // so we'll need to tell nvim our new size. - let grid = state.grids.get(&1).unwrap(); - let (rows, cols) = - grid.calc_size_for_new_metrics().unwrap(); - let mut nvim = nvim.lock().unwrap(); - nvim.ui_try_resize_async(cols as i64, rows as i64) - .cb(|res| { - if let Err(err) = res { - eprintln!("Error: failed to resize nvim on line space change ({:?})", err); - } - }) - .call(); - - state.cmdline.set_line_space(*val); - state - .popupmenu - .set_line_space(*val, &state.hl_defs); - state.tabline.set_line_space(*val, &state.hl_defs); - } - OptionSet::NotSupported(name) => { - println!("Not supported option set: {}", name); - } + evt.iter().for_each(|opt| match opt { + OptionSet::GuiFont(font) => { + let font = + Font::from_guifont(font).unwrap_or(Font::default()); + + let mut opts = + state.resize_on_flush.take().unwrap_or_else(|| { + let grid = state.grids.get(&1).unwrap(); + ResizeOptions { + font: grid.get_font(), + line_space: grid.get_line_space(), + } + }); + + opts.font = font; + + state.resize_on_flush = Some(opts); + } + OptionSet::LineSpace(val) => { + let mut opts = + state.resize_on_flush.take().unwrap_or_else(|| { + let grid = state.grids.get(&1).unwrap(); + ResizeOptions { + font: grid.get_font(), + line_space: grid.get_line_space(), + } + }); + + opts.line_space = *val; + + state.resize_on_flush = Some(opts); + } + OptionSet::NotSupported(name) => { + println!("Not supported option set: {}", name); } }); } @@ -698,6 +669,45 @@ fn handle_redraw_event( for grid in state.grids.values() { grid.flush(&state.hl_defs); } + + if let Some(opts) = state.resize_on_flush.take() { + for grid in state.grids.values() { + grid.update_cell_metrics( + opts.font.clone(), + opts.line_space, + ); + } + + let grid = state.grids.get(&1).unwrap(); + let (cols, rows) = grid.calc_size(); + + // Cancel any possible delayed call for ui_try_resize. + let mut id = state.resize_source_id.lock().unwrap(); + if let Some(id) = id.take() { + glib::source::source_remove(id); + } + + nvim.lock().unwrap().ui_try_resize_async(cols as i64, rows as i64) + .cb(|res| { + if let Err(err) = res { + eprintln!("Error: failed to resize nvim on line space change ({:?})", err); + } + }) + .call(); + + state.popupmenu.set_font(opts.font.clone(), &state.hl_defs); + state.cmdline.set_font(opts.font.clone(), &state.hl_defs); + state.tabline.set_font(opts.font.clone(), &state.hl_defs); + state.cursor_tooltip.set_font(opts.font.clone()); + + state.cmdline.set_line_space(opts.line_space); + state + .popupmenu + .set_line_space(opts.line_space, &state.hl_defs); + state + .tabline + .set_line_space(opts.line_space, &state.hl_defs); + } } RedrawEvent::PopupmenuShow(evt) => { evt.iter().for_each(|popupmenu| {