From 05dee3251f12d9f43331f15b79dfc29ea3ea99f2 Mon Sep 17 00:00:00 2001 From: daa84 Date: Tue, 5 Sep 2017 18:23:46 +0300 Subject: [PATCH] Get sizes from FontMetrics --- src/color.rs | 6 +- src/render/context.rs | 72 ++++++++++--- src/render/mod.rs | 19 +++- src/shell.rs | 243 ++++++++++++++++++------------------------ 4 files changed, 177 insertions(+), 163 deletions(-) diff --git a/src/color.rs b/src/color.rs index b2de55b..9e19c2c 100644 --- a/src/color.rs +++ b/src/color.rs @@ -52,15 +52,15 @@ impl ColorModel { } } - pub fn cell_colors<'a>(&'a self, cell: &'a Cell) -> (&'a Color, &'a Color) { + pub fn cell_colors<'a>(&'a self, cell: &'a Cell) -> (Option<&'a Color>, &'a Color) { if !cell.attrs.reverse { ( - cell.attrs.background.as_ref().unwrap_or(&self.bg_color), + cell.attrs.background.as_ref(), cell.attrs.foreground.as_ref().unwrap_or(&self.fg_color), ) } else { ( - cell.attrs.foreground.as_ref().unwrap_or(&self.fg_color), + cell.attrs.foreground.as_ref().or(Some(&self.fg_color)), cell.attrs.background.as_ref().unwrap_or(&self.bg_color), ) } diff --git a/src/render/context.rs b/src/render/context.rs index 9b1fde6..5149008 100644 --- a/src/render/context.rs +++ b/src/render/context.rs @@ -1,5 +1,3 @@ -use std::ffi::CString; - use pangocairo::FontMap; use pango::prelude::*; use pango; @@ -9,31 +7,77 @@ use sys::pango as sys_pango; use ui_model::StyledLine; pub struct Context { - pango_context: pango::Context, + state: ContextState, } impl Context { - pub fn new(font_desc: &pango::FontDescription) -> Self { - Context { pango_context: create_pango_context(font_desc) } + pub fn new(font_desc: pango::FontDescription) -> Self { + Context { state: ContextState::new(font_desc) } } - pub fn update(&mut self, font_desc: &pango::FontDescription) { - self.pango_context = create_pango_context(font_desc); + pub fn update(&mut self, font_desc: pango::FontDescription) { + self.state = ContextState::new(font_desc); } pub fn itemize(&self, line: &StyledLine) -> Vec { sys_pango::pango_itemize( - &self.pango_context, + &self.state.pango_context, line.line_str.trim_right(), &line.attr_list, ) } + + #[inline] + pub fn font_description(&self) -> &pango::FontDescription { + &self.state.font_desc + } + + #[inline] + pub fn ascent(&self) -> f64 { + self.state.cell_metrics.ascent + } + + #[inline] + pub fn cell_metrics(&self) -> &CellMetrics { + &self.state.cell_metrics + } } -fn create_pango_context(font_desc: &pango::FontDescription) -> pango::Context { - let font_map = FontMap::get_default(); - let pango_context = font_map.create_context().unwrap(); - pango_context.set_font_description(&font_desc); - - pango_context +struct ContextState { + pango_context: pango::Context, + cell_metrics: CellMetrics, + font_desc: pango::FontDescription, +} + +impl ContextState { + pub fn new(font_desc: pango::FontDescription) -> Self { + let font_map = FontMap::get_default(); + let pango_context = font_map.create_context().unwrap(); + pango_context.set_font_description(&font_desc); + + let font_metrics = pango_context.get_metrics(None, None).unwrap(); + + ContextState { + pango_context, + cell_metrics: CellMetrics::new(&font_metrics), + font_desc, + } + } +} + +pub struct CellMetrics { + pub line_height: f64, + pub char_width: f64, + pub ascent: f64, +} + +impl CellMetrics { + fn new(font_metrics: &pango::FontMetrics) -> Self { + CellMetrics { + ascent: font_metrics.get_ascent() as f64 / pango::SCALE as f64, + line_height: (font_metrics.get_ascent() + font_metrics.get_descent()) as f64 / + pango::SCALE as f64, + char_width: font_metrics.get_approximate_digit_width() as f64 / pango::SCALE as f64, + } + } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 15a9c72..c89f95c 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,6 +1,7 @@ mod context; pub use self::context::Context; +pub use self::context::CellMetrics; use color; use sys::pango::*; @@ -11,10 +12,9 @@ use ui_model; pub fn render( ctx: &cairo::Context, + font_ctx: &context::Context, ui_model: &ui_model::UiModel, color_model: &color::ColorModel, - line_height: f64, - char_width: f64, ) { ctx.set_source_rgb( color_model.bg_color.0, @@ -23,16 +23,25 @@ pub fn render( ); ctx.paint(); - let mut line_y = line_height; + let &CellMetrics {line_height, char_width, ..} = font_ctx.cell_metrics(); + let mut line_y = 0.0; + let ascent = font_ctx.ascent(); for line in ui_model.model() { let mut line_x = 0.0; for i in 0..line.line.len() { - ctx.move_to(line_x, line_y); + let (bg, fg) = color_model.cell_colors(&line.line[i]); + + if let Some(bg) = bg { + ctx.set_source_rgb(bg.0, bg.1, bg.2); + ctx.rectangle(line_x, line_y, char_width, line_height); + ctx.fill(); + } + if let Some(item) = line.item_line[i].as_ref() { if let Some(ref glyphs) = item.glyphs { - let (_, fg) = color_model.cell_colors(&line.line[i]); + ctx.move_to(line_x, line_y + ascent); ctx.set_source_rgb(fg.0, fg.1, fg.2); ctx.show_glyph_string(item.font(), glyphs); } diff --git a/src/shell.rs b/src/shell.rs index 82712d0..4b5aa6d 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -32,6 +32,7 @@ use tabline::Tabline; use error; use mode; use render; +use render::CellMetrics; const DEFAULT_FONT_NAME: &str = "DejaVu Sans Mono 12"; pub const MINIMUM_SUPPORTED_NVIM_VERSION: &str = "0.2"; @@ -54,7 +55,7 @@ pub struct State { cur_attrs: Option, mouse_enabled: bool, nvim: Rc>, - font_desc: FontDescription, + font_ctx: render::Context, cursor: Option, popup_menu: RefCell, settings: Rc>, @@ -67,8 +68,6 @@ pub struct State { im_context: gtk::IMMulticontext, error_area: error::ErrorArea, - line_height: Option, - char_width: Option, request_resize: bool, request_nvim_resize: bool, resize_timer: Option, @@ -82,6 +81,7 @@ impl State { pub fn new(settings: Rc>, options: ShellOptions) -> State { let drawing_area = gtk::DrawingArea::new(); let popup_menu = RefCell::new(PopupMenu::new(&drawing_area)); + let font_ctx = render::Context::new(FontDescription::from_string(DEFAULT_FONT_NAME)); State { model: UiModel::new(1, 1), @@ -89,10 +89,10 @@ impl State { nvim: Rc::new(RefCell::new(NeovimClient::new())), cur_attrs: None, mouse_enabled: true, - font_desc: FontDescription::from_string(DEFAULT_FONT_NAME), + font_ctx, cursor: None, popup_menu, - settings: settings, + settings, mode: mode::Mode::new(), @@ -103,8 +103,6 @@ impl State { im_context: gtk::IMMulticontext::new(), error_area: error::ErrorArea::new(), - line_height: None, - char_width: None, resize_timer: None, request_resize: false, request_nvim_resize: false, @@ -142,18 +140,12 @@ impl State { } } - fn create_pango_font(&self) -> FontDescription { - self.font_desc.clone() - } - pub fn get_font_desc(&self) -> &FontDescription { - &self.font_desc + self.font_ctx.font_description() } pub fn set_font_desc(&mut self, desc: &str) { - self.font_desc = FontDescription::from_string(desc); - self.line_height = None; - self.char_width = None; + self.font_ctx.update(FontDescription::from_string(desc)); self.model.clear_draw_cache(); } @@ -183,19 +175,19 @@ impl State { } fn queue_draw_area>(&self, rect_list: &[M]) { - match (&self.line_height, &self.char_width) { - (&Some(line_height), &Some(char_width)) => { - for rect in rect_list { - let mut rect = rect.as_ref().clone(); - // this need to repain also line under curren line - // in case underscore or 'g' symbol is go here - // right one for italic symbol - rect.extend(0, 1, 0, 1); - let (x, y, width, height) = rect.to_area(line_height, char_width); - self.drawing_area.queue_draw_area(x, y, width, height); - } - } - _ => self.drawing_area.queue_draw(), + let &CellMetrics { + line_height, + char_width, + .. + } = self.font_ctx.cell_metrics(); + for rect in rect_list { + let mut rect = rect.as_ref().clone(); + // this need to repain also line under curren line + // in case underscore or 'g' symbol is go here + // right one for italic symbol + rect.extend(0, 1, 0, 1); + let (x, y, width, height) = rect.to_area(line_height, char_width); + self.drawing_area.queue_draw_area(x, y, width, height); } } @@ -203,36 +195,17 @@ impl State { input::im_input(&mut self.nvim.borrow_mut(), ch); } - fn calc_char_bounds(&self, ctx: &cairo::Context) -> (i32, i32) { - let layout = ctx.create_pango_layout(); - - let desc = self.create_pango_font(); - layout.set_font_description(Some(&desc)); - layout.set_text("A"); - - layout.get_pixel_size() - } - - fn calc_line_metrics(&mut self, ctx: &cairo::Context) { - if self.line_height.is_none() { - let (width, height) = self.calc_char_bounds(ctx); - self.line_height = Some(height as f64); - self.char_width = Some(width as f64); - } - } - - fn calc_nvim_size(&self) -> Option<(usize, usize)> { - if let Some(line_height) = self.line_height { - if let Some(char_width) = self.char_width { - let alloc = self.drawing_area.get_allocation(); - return Some(( - (alloc.width as f64 / char_width).trunc() as usize, - (alloc.height as f64 / line_height).trunc() as usize, - )); - } - } - - None + fn calc_nvim_size(&self) -> (usize, usize) { + let &CellMetrics { + line_height, + char_width, + .. + } = self.font_ctx.cell_metrics(); + let alloc = self.drawing_area.get_allocation(); + ( + (alloc.width as f64 / char_width).trunc() as usize, + (alloc.height as f64 / line_height).trunc() as usize, + ) } fn show_error_area(&self) { @@ -244,21 +217,21 @@ impl State { } fn set_im_location(&self) { - if let Some(line_height) = self.line_height { - if let Some(char_width) = self.char_width { - let (row, col) = self.model.get_cursor(); + let &CellMetrics { + line_height, + char_width, + .. + } = self.font_ctx.cell_metrics(); + let (row, col) = self.model.get_cursor(); - let (x, y, width, height) = - ModelRect::point(col, row).to_area(line_height, char_width); + let (x, y, width, height) = ModelRect::point(col, row).to_area(line_height, char_width); - self.im_context.set_cursor_location(&gdk::Rectangle { - x, - y, - width, - height, - }); - } - } + self.im_context.set_cursor_location(&gdk::Rectangle { + x, + y, + width, + height, + }); self.im_context.reset(); } @@ -562,19 +535,19 @@ fn gtk_button_press(shell: &mut State, ui_state: &mut UiState, ev: &EventButton) } fn mouse_input(shell: &mut State, input: &str, state: ModifierType, position: (f64, f64)) { - if let Some(line_height) = shell.line_height { - if let Some(char_width) = shell.char_width { - - let mut nvim = shell.nvim(); - let (x, y) = position; - let col = (x / char_width).trunc() as u64; - let row = (y / line_height).trunc() as u64; - let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row); - nvim.input(&input_str).expect( - "Can't send mouse input event", - ); - } - } + let &CellMetrics { + line_height, + char_width, + .. + } = shell.font_ctx.cell_metrics(); + let mut nvim = shell.nvim(); + let (x, y) = position; + let col = (x / char_width).trunc() as u64; + let row = (y / line_height).trunc() as u64; + let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row); + nvim.input(&input_str).expect( + "Can't send mouse input event", + ); } fn gtk_button_release(ui_state: &mut UiState) -> Inhibit { @@ -589,18 +562,7 @@ fn gtk_motion_notify(shell: &mut State, ui_state: &mut UiState, ev: &EventMotion Inhibit(false) } -#[inline] -fn update_line_metrics(state_arc: &Arc>, ctx: &cairo::Context) { - let mut state = state_arc.borrow_mut(); - - if state.line_height.is_none() { - state.calc_line_metrics(ctx); - } -} - fn gtk_draw(state_arc: &Arc>, ctx: &cairo::Context) -> Inhibit { - update_line_metrics(state_arc, ctx); - if state_arc.borrow_mut().request_nvim_resize { try_nvim_resize(state_arc); } @@ -619,20 +581,8 @@ fn gtk_draw(state_arc: &Arc>, ctx: &cairo::Context) -> Inhibit { } fn render(state: &mut State, ctx: &cairo::Context) { - let font_desc = state.create_pango_font(); - let line_height = state.line_height.unwrap(); - let char_width = state.char_width.unwrap(); - - let font_ctx = render::Context::new(&font_desc); - - render::shape_dirty(&font_ctx, &mut state.model, &state.color_model); - render::render( - ctx, - &state.model, - &state.color_model, - line_height, - char_width, - ); + render::shape_dirty(&state.font_ctx, &mut state.model, &state.color_model); + render::render(ctx, &state.font_ctx, &state.model, &state.color_model); } fn show_nvim_start_error(err: nvim::NvimInitError, state_arc: Arc>) { @@ -715,7 +665,7 @@ fn init_nvim(state_arc: &Arc>) { if nvim.is_uninitialized() { nvim.set_in_progress(); - let (cols, rows) = state.calc_nvim_size().unwrap(); + let (cols, rows) = state.calc_nvim_size(); let state_arc = state_arc.clone(); let options = state.options.clone(); @@ -779,10 +729,13 @@ fn get_model_clip( fn draw_initializing(state: &State, ctx: &cairo::Context) { let layout = ctx.create_pango_layout(); - let desc = state.create_pango_font(); + let desc = state.get_font_desc(); let alloc = state.drawing_area.get_allocation(); - let line_height = state.line_height.unwrap(); - let char_width = state.char_width.unwrap(); + let &CellMetrics { + line_height, + char_width, + .. + } = state.font_ctx.cell_metrics(); ctx.set_source_rgb( state.color_model.bg_color.0, @@ -791,7 +744,7 @@ fn draw_initializing(state: &State, ctx: &cairo::Context) { ); ctx.paint(); - layout.set_font_description(&desc); + layout.set_font_description(desc); layout.set_text("Loading->"); let (width, height) = layout.get_pixel_size(); @@ -964,12 +917,17 @@ fn request_window_resize(state: &mut State) { return; } + let &CellMetrics { + line_height, + char_width, + .. + } = state.font_ctx.cell_metrics(); state.request_resize = false; let width = state.drawing_area.get_allocated_width(); let height = state.drawing_area.get_allocated_height(); - let request_height = (state.model.rows as f64 * state.line_height.unwrap()) as i32; - let request_width = (state.model.columns as f64 * state.char_width.unwrap()) as i32; + let request_height = (state.model.rows as f64 * line_height) as i32; + let request_width = (state.model.columns as f64 * char_width) as i32; if width != request_width || height != request_height { let window: gtk::Window = state @@ -998,21 +956,20 @@ fn try_nvim_resize(state: &Arc>) { return; } - if let Some((columns, rows)) = state_ref.calc_nvim_size() { - let state = state.clone(); - state_ref.resize_timer = Some(glib::timeout_add(250, move || { - let mut state_ref = state.borrow_mut(); + let (columns, rows) = state_ref.calc_nvim_size(); + let state = state.clone(); + state_ref.resize_timer = Some(glib::timeout_add(250, move || { + let mut state_ref = state.borrow_mut(); - state_ref.resize_timer = None; + state_ref.resize_timer = None; - if state_ref.model.rows != rows || state_ref.model.columns != columns { - if let Err(err) = state_ref.nvim().ui_try_resize(columns as u64, rows as u64) { - error!("Error trying resize nvim {}", err); - } + if state_ref.model.rows != rows || state_ref.model.columns != columns { + if let Err(err) = state_ref.nvim().ui_try_resize(columns as u64, rows as u64) { + error!("Error trying resize nvim {}", err); } - Continue(false) - })); - } + } + Continue(false) + })); } impl RedrawEvents for State { @@ -1149,20 +1106,24 @@ impl RedrawEvents for State { row: u64, col: u64, ) -> RepaintMode { - if let (&Some(line_height), &Some(char_width)) = (&self.line_height, &self.char_width) { - let point = ModelRect::point(col as usize, row as usize); - let (x, y, width, height) = point.to_area(line_height, char_width); - self.popup_menu.borrow_mut().show( - self, - menu, - selected, - x, - y, - width, - height, - ); - } + let &CellMetrics { + line_height, + char_width, + .. + } = self.font_ctx.cell_metrics(); + let point = ModelRect::point(col as usize, row as usize); + let (x, y, width, height) = point.to_area(line_height, char_width); + + self.popup_menu.borrow_mut().show( + self, + menu, + selected, + x, + y, + width, + height, + ); RepaintMode::Nothing }