diff options
Diffstat (limited to 'alacritty/src/display/mod.rs')
-rw-r--r-- | alacritty/src/display/mod.rs | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs new file mode 100644 index 00000000..2a55402e --- /dev/null +++ b/alacritty/src/display/mod.rs @@ -0,0 +1,823 @@ +//! The display subsystem including window management, font rasterization, and +//! GPU drawing. + +use std::cmp::min; +use std::f64; +use std::fmt::{self, Formatter}; +#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] +use std::sync::atomic::Ordering; +use std::time::Instant; + +use glutin::dpi::{PhysicalPosition, PhysicalSize}; +use glutin::event::ModifiersState; +use glutin::event_loop::EventLoop; +#[cfg(not(any(target_os = "macos", windows)))] +use glutin::platform::unix::EventLoopWindowTargetExtUnix; +use glutin::window::CursorIcon; +use log::{debug, info}; +use parking_lot::MutexGuard; +use unicode_width::UnicodeWidthChar; +#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] +use wayland_client::{Display as WaylandDisplay, EventQueue}; + +use crossfont::{self, Rasterize, Rasterizer}; + +use alacritty_terminal::ansi::NamedColor; +use alacritty_terminal::event::{EventListener, OnResize}; +use alacritty_terminal::grid::Dimensions as _; +use alacritty_terminal::index::{Column, Direction, Line, Point}; +use alacritty_terminal::selection::Selection; +use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLS, MIN_SCREEN_LINES}; + +use crate::config::font::Font; +use crate::config::window::Dimensions; +#[cfg(not(windows))] +use crate::config::window::StartupMode; +use crate::config::Config; +use crate::display::bell::VisualBell; +use crate::display::color::List; +use crate::display::content::RenderableContent; +use crate::display::cursor::IntoRects; +use crate::display::meter::Meter; +use crate::display::window::Window; +use crate::event::{Mouse, SearchState}; +use crate::message_bar::{MessageBuffer, MessageType}; +use crate::renderer::rects::{RenderLines, RenderRect}; +use crate::renderer::{self, GlyphCache, QuadRenderer}; +use crate::url::{Url, Urls}; + +pub mod content; +pub mod cursor; +pub mod window; + +mod bell; +mod color; +mod meter; +#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] +mod wayland_theme; + +const FORWARD_SEARCH_LABEL: &str = "Search: "; +const BACKWARD_SEARCH_LABEL: &str = "Backward Search: "; + +#[derive(Debug)] +pub enum Error { + /// Error with window management. + Window(window::Error), + + /// Error dealing with fonts. + Font(crossfont::Error), + + /// Error in renderer. + Render(renderer::Error), + + /// Error during buffer swap. + ContextError(glutin::ContextError), +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Window(err) => err.source(), + Error::Font(err) => err.source(), + Error::Render(err) => err.source(), + Error::ContextError(err) => err.source(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Error::Window(err) => err.fmt(f), + Error::Font(err) => err.fmt(f), + Error::Render(err) => err.fmt(f), + Error::ContextError(err) => err.fmt(f), + } + } +} + +impl From<window::Error> for Error { + fn from(val: window::Error) -> Self { + Error::Window(val) + } +} + +impl From<crossfont::Error> for Error { + fn from(val: crossfont::Error) -> Self { + Error::Font(val) + } +} + +impl From<renderer::Error> for Error { + fn from(val: renderer::Error) -> Self { + Error::Render(val) + } +} + +impl From<glutin::ContextError> for Error { + fn from(val: glutin::ContextError) -> Self { + Error::ContextError(val) + } +} + +#[derive(Default, Clone, Debug, PartialEq)] +pub struct DisplayUpdate { + pub dirty: bool, + + dimensions: Option<PhysicalSize<u32>>, + cursor_dirty: bool, + font: Option<Font>, +} + +impl DisplayUpdate { + pub fn dimensions(&self) -> Option<PhysicalSize<u32>> { + self.dimensions + } + + pub fn font(&self) -> Option<&Font> { + self.font.as_ref() + } + + pub fn cursor_dirty(&self) -> bool { + self.cursor_dirty + } + + pub fn set_dimensions(&mut self, dimensions: PhysicalSize<u32>) { + self.dimensions = Some(dimensions); + self.dirty = true; + } + + pub fn set_font(&mut self, font: Font) { + self.font = Some(font); + self.dirty = true; + } + + pub fn set_cursor_dirty(&mut self) { + self.cursor_dirty = true; + self.dirty = true; + } +} + +/// The display wraps a window, font rasterizer, and GPU renderer. +pub struct Display { + pub size_info: SizeInfo, + pub window: Window, + pub urls: Urls, + + /// Currently highlighted URL. + pub highlighted_url: Option<Url>, + + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + pub wayland_event_queue: Option<EventQueue>, + + #[cfg(not(any(target_os = "macos", windows)))] + pub is_x11: bool, + + /// UI cursor visibility for blinking. + pub cursor_hidden: bool, + + pub visual_bell: VisualBell, + + /// Mapped RGB values for each terminal color. + pub colors: List, + + renderer: QuadRenderer, + glyph_cache: GlyphCache, + meter: Meter, +} + +impl Display { + pub fn new<E>(config: &Config, event_loop: &EventLoop<E>) -> Result<Display, Error> { + // Guess DPR based on first monitor. + let estimated_dpr = + event_loop.available_monitors().next().map(|m| m.scale_factor()).unwrap_or(1.); + + // Guess the target window dimensions. + let metrics = GlyphCache::static_metrics(config.ui_config.font.clone(), estimated_dpr)?; + let (cell_width, cell_height) = compute_cell_size(config, &metrics); + + // Guess the target window size if the user has specified the number of lines/columns. + let dimensions = config.ui_config.window.dimensions(); + let estimated_size = dimensions.map(|dimensions| { + window_size(config, dimensions, cell_width, cell_height, estimated_dpr) + }); + + debug!("Estimated DPR: {}", estimated_dpr); + debug!("Estimated window size: {:?}", estimated_size); + debug!("Estimated cell size: {} x {}", cell_width, cell_height); + + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + let mut wayland_event_queue = None; + + // Initialize Wayland event queue, to handle Wayland callbacks. + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + if let Some(display) = event_loop.wayland_display() { + let display = unsafe { WaylandDisplay::from_external_display(display as _) }; + wayland_event_queue = Some(display.create_event_queue()); + } + + // Spawn the Alacritty window. + let mut window = Window::new( + event_loop, + &config, + estimated_size, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + wayland_event_queue.as_ref(), + )?; + + info!("Device pixel ratio: {}", window.dpr); + + // Create renderer. + let mut renderer = QuadRenderer::new()?; + + let (glyph_cache, cell_width, cell_height) = + Self::new_glyph_cache(window.dpr, &mut renderer, config)?; + + if let Some(dimensions) = dimensions { + if (estimated_dpr - window.dpr).abs() < f64::EPSILON { + info!("Estimated DPR correctly, skipping resize"); + } else { + // Resize the window again if the DPR was not estimated correctly. + let size = window_size(config, dimensions, cell_width, cell_height, window.dpr); + window.set_inner_size(size); + } + } + + let padding = config.ui_config.window.padding(window.dpr); + let viewport_size = window.inner_size(); + + // Create new size with at least one column and row. + let size_info = SizeInfo::new( + viewport_size.width as f32, + viewport_size.height as f32, + cell_width, + cell_height, + padding.0, + padding.1, + config.ui_config.window.dynamic_padding && dimensions.is_none(), + ); + + info!("Cell size: {} x {}", cell_width, cell_height); + info!("Padding: {} x {}", size_info.padding_x(), size_info.padding_y()); + info!("Width: {}, Height: {}", size_info.width(), size_info.height()); + + // Update OpenGL projection. + renderer.resize(&size_info); + + // Clear screen. + let background_color = config.ui_config.colors.primary.background; + renderer.with_api(&config.ui_config, &size_info, |api| { + api.clear(background_color); + }); + + // Set subpixel anti-aliasing. + #[cfg(target_os = "macos")] + crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); + + // Disable shadows for transparent windows on macOS. + #[cfg(target_os = "macos")] + window.set_has_shadow(config.ui_config.background_opacity() >= 1.0); + + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + let is_x11 = event_loop.is_x11(); + #[cfg(not(any(feature = "x11", target_os = "macos", windows)))] + let is_x11 = false; + + // On Wayland we can safely ignore this call, since the window isn't visible until you + // actually draw something into it and commit those changes. + #[cfg(not(any(target_os = "macos", windows)))] + if is_x11 { + window.swap_buffers(); + renderer.with_api(&config.ui_config, &size_info, |api| { + api.finish(); + }); + } + + window.set_visible(true); + + // Set window position. + // + // TODO: replace `set_position` with `with_position` once available. + // Upstream issue: https://github.com/rust-windowing/winit/issues/806. + if let Some(position) = config.ui_config.window.position { + window.set_outer_position(PhysicalPosition::from((position.x, position.y))); + } + + #[allow(clippy::single_match)] + #[cfg(not(windows))] + match config.ui_config.window.startup_mode { + #[cfg(target_os = "macos")] + StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), + #[cfg(not(target_os = "macos"))] + StartupMode::Maximized if is_x11 => window.set_maximized(true), + _ => (), + } + + Ok(Self { + window, + renderer, + glyph_cache, + meter: Meter::new(), + size_info, + urls: Urls::new(), + highlighted_url: None, + #[cfg(not(any(target_os = "macos", windows)))] + is_x11, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + wayland_event_queue, + cursor_hidden: false, + visual_bell: VisualBell::from(&config.ui_config.bell), + colors: List::from(&config.ui_config.colors), + }) + } + + fn new_glyph_cache( + dpr: f64, + renderer: &mut QuadRenderer, + config: &Config, + ) -> Result<(GlyphCache, f32, f32), Error> { + let font = config.ui_config.font.clone(); + let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes)?; + + // Initialize glyph cache. + let glyph_cache = { + info!("Initializing glyph cache..."); + let init_start = Instant::now(); + + let cache = + renderer.with_loader(|mut api| GlyphCache::new(rasterizer, &font, &mut api))?; + + let stop = init_start.elapsed(); + let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64; + info!("... finished initializing glyph cache in {}s", stop_f); + + cache + }; + + // Need font metrics to resize the window properly. This suggests to me the + // font metrics should be computed before creating the window in the first + // place so that a resize is not needed. + let (cw, ch) = compute_cell_size(config, &glyph_cache.font_metrics()); + + Ok((glyph_cache, cw, ch)) + } + + /// Update font size and cell dimensions. + /// + /// This will return a tuple of the cell width and height. + fn update_glyph_cache(&mut self, config: &Config, font: &Font) -> (f32, f32) { + let cache = &mut self.glyph_cache; + let dpr = self.window.dpr; + + self.renderer.with_loader(|mut api| { + let _ = cache.update_font_size(font, dpr, &mut api); + }); + + // Compute new cell sizes. + compute_cell_size(config, &self.glyph_cache.font_metrics()) + } + + /// Clear glyph cache. + fn clear_glyph_cache(&mut self) { + let cache = &mut self.glyph_cache; + self.renderer.with_loader(|mut api| { + cache.clear_glyph_cache(&mut api); + }); + } + + /// Process update events. + pub fn handle_update<T>( + &mut self, + terminal: &mut Term<T>, + pty_resize_handle: &mut dyn OnResize, + message_buffer: &MessageBuffer, + search_active: bool, + config: &Config, + update_pending: DisplayUpdate, + ) where + T: EventListener, + { + let (mut cell_width, mut cell_height) = + (self.size_info.cell_width(), self.size_info.cell_height()); + + // Update font size and cell dimensions. + if let Some(font) = update_pending.font() { + let cell_dimensions = self.update_glyph_cache(config, font); + cell_width = cell_dimensions.0; + cell_height = cell_dimensions.1; + + info!("Cell size: {} x {}", cell_width, cell_height); + } else if update_pending.cursor_dirty() { + self.clear_glyph_cache(); + } + + let (mut width, mut height) = (self.size_info.width(), self.size_info.height()); + if let Some(dimensions) = update_pending.dimensions() { + width = dimensions.width as f32; + height = dimensions.height as f32; + } + + let padding = config.ui_config.window.padding(self.window.dpr); + + self.size_info = SizeInfo::new( + width, + height, + cell_width, + cell_height, + padding.0, + padding.1, + config.ui_config.window.dynamic_padding, + ); + + // Update number of column/lines in the viewport. + let message_bar_lines = + message_buffer.message().map(|m| m.text(&self.size_info).len()).unwrap_or(0); + let search_lines = if search_active { 1 } else { 0 }; + self.size_info.reserve_lines(message_bar_lines + search_lines); + + // Resize PTY. + pty_resize_handle.on_resize(&self.size_info); + + // Resize terminal. + terminal.resize(self.size_info); + + // Resize renderer. + let physical = + PhysicalSize::new(self.size_info.width() as u32, self.size_info.height() as u32); + self.window.resize(physical); + self.renderer.resize(&self.size_info); + + info!("Padding: {} x {}", self.size_info.padding_x(), self.size_info.padding_y()); + info!("Width: {}, Height: {}", self.size_info.width(), self.size_info.height()); + } + + /// Draw the screen. + /// + /// A reference to Term whose state is being drawn must be provided. + /// + /// This call may block if vsync is enabled. + pub fn draw<T: EventListener>( + &mut self, + terminal: MutexGuard<'_, Term<T>>, + message_buffer: &MessageBuffer, + config: &Config, + mouse: &Mouse, + mods: ModifiersState, + search_state: &SearchState, + ) { + // Convert search match from viewport to absolute indexing. + let search_active = search_state.regex().is_some(); + let viewport_match = search_state + .focused_match() + .and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match)); + let cursor_hidden = self.cursor_hidden || search_state.regex().is_some(); + + // Collect renderable content before the terminal is dropped. + let dfas = search_state.dfas(); + let colors = &self.colors; + let mut content = RenderableContent::new(&terminal, dfas, config, colors, !cursor_hidden); + let mut grid_cells = Vec::new(); + while let Some(cell) = content.next() { + grid_cells.push(cell); + } + let background_color = content.color(NamedColor::Background as usize); + let display_offset = content.display_offset(); + let cursor = content.cursor(); + + let cursor_point = terminal.grid().cursor.point; + let total_lines = terminal.grid().total_lines(); + let metrics = self.glyph_cache.font_metrics(); + let size_info = self.size_info; + + let selection = !terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true); + let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE) + && !terminal.mode().contains(TermMode::VI); + + let vi_mode = terminal.mode().contains(TermMode::VI); + let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None }; + + // Drop terminal as early as possible to free lock. + drop(terminal); + + self.renderer.with_api(&config.ui_config, &size_info, |api| { + api.clear(background_color); + }); + + let mut lines = RenderLines::new(); + let mut urls = Urls::new(); + + // Draw grid. + { + let _sampler = self.meter.sampler(); + + let glyph_cache = &mut self.glyph_cache; + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + // Iterate over all non-empty cells in the grid. + for mut cell in grid_cells { + // Invert the active match during search. + if cell.is_match + && viewport_match + .as_ref() + .map_or(false, |viewport_match| viewport_match.contains(&cell.point)) + { + let colors = config.ui_config.colors.search.focused_match; + let match_fg = colors.foreground.color(cell.fg, cell.bg); + cell.bg = colors.background.color(cell.fg, cell.bg); + cell.fg = match_fg; + cell.bg_alpha = 1.0; + } + + // Update URL underlines. + urls.update(size_info.cols(), &cell); + + // Update underline/strikeout. + lines.update(&cell); + + // Draw the cell. + api.render_cell(cell, glyph_cache); + } + }); + } + + let mut rects = lines.rects(&metrics, &size_info); + + // Update visible URLs. + self.urls = urls; + if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) { + rects.append(&mut url.rects(&metrics, &size_info)); + + self.window.set_mouse_cursor(CursorIcon::Hand); + + self.highlighted_url = Some(url); + } else if self.highlighted_url.is_some() { + self.highlighted_url = None; + + if mouse_mode { + self.window.set_mouse_cursor(CursorIcon::Default); + } else { + self.window.set_mouse_cursor(CursorIcon::Text); + } + } + + if let Some(vi_mode_cursor) = vi_mode_cursor { + // Highlight URLs at the vi mode cursor position. + let vi_mode_point = vi_mode_cursor.point; + if let Some(url) = self.urls.find_at(vi_mode_point) { + rects.append(&mut url.rects(&metrics, &size_info)); + } + + // Indicate vi mode by showing the cursor's position in the top right corner. + let line = size_info.screen_lines() + display_offset - vi_mode_point.line - 1; + self.draw_line_indicator(config, &size_info, total_lines, Some(vi_mode_point), line.0); + } else if search_active { + // Show current display offset in vi-less search to indicate match position. + self.draw_line_indicator(config, &size_info, total_lines, None, display_offset); + } + + // Push the cursor rects for rendering. + if let Some(cursor) = cursor { + for rect in cursor.rects(&size_info, config.cursor.thickness()) { + rects.push(rect); + } + } + + // Push visual bell after url/underline/strikeout rects. + let visual_bell_intensity = self.visual_bell.intensity(); + if visual_bell_intensity != 0. { + let visual_bell_rect = RenderRect::new( + 0., + 0., + size_info.width(), + size_info.height(), + config.ui_config.bell.color, + visual_bell_intensity as f32, + ); + rects.push(visual_bell_rect); + } + + if let Some(message) = message_buffer.message() { + let search_offset = if search_active { 1 } else { 0 }; + let text = message.text(&size_info); + + // Create a new rectangle for the background. + let start_line = size_info.screen_lines() + search_offset; + let y = size_info.cell_height().mul_add(start_line.0 as f32, size_info.padding_y()); + + let bg = match message.ty() { + MessageType::Error => config.ui_config.colors.normal.red, + MessageType::Warning => config.ui_config.colors.normal.yellow, + }; + + let message_bar_rect = + RenderRect::new(0., y, size_info.width(), size_info.height() - y, bg, 1.); + + // Push message_bar in the end, so it'll be above all other content. + rects.push(message_bar_rect); + + // Draw rectangles. + self.renderer.draw_rects(&size_info, rects); + + // Relay messages to the user. + let glyph_cache = &mut self.glyph_cache; + let fg = config.ui_config.colors.primary.background; + for (i, message_text) in text.iter().enumerate() { + let point = Point::new(start_line + i, Column(0)); + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + api.render_string(glyph_cache, point, fg, bg, &message_text); + }); + } + } else { + // Draw rectangles. + self.renderer.draw_rects(&size_info, rects); + } + + self.draw_render_timer(config, &size_info); + + // Handle search and IME positioning. + let ime_position = match search_state.regex() { + Some(regex) => { + let search_label = match search_state.direction() { + Direction::Right => FORWARD_SEARCH_LABEL, + Direction::Left => BACKWARD_SEARCH_LABEL, + }; + + let search_text = Self::format_search(&size_info, regex, search_label); + + // Render the search bar. + self.draw_search(config, &size_info, &search_text); + + // Compute IME position. + Point::new(size_info.screen_lines() + 1, Column(search_text.chars().count() - 1)) + }, + None => cursor_point, + }; + + // Update IME position. + self.window.update_ime_position(ime_position, &self.size_info); + + // Frame event should be requested before swaping buffers, since it requires surface + // `commit`, which is done by swap buffers under the hood. + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + self.request_frame(&self.window); + + self.window.swap_buffers(); + + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + if self.is_x11 { + // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command + // will block to synchronize (this is `glClear` in Alacritty), which causes a + // permanent one frame delay. + self.renderer.with_api(&config.ui_config, &size_info, |api| { + api.finish(); + }); + } + } + + /// Update to a new configuration. + pub fn update_config(&mut self, config: &Config) { + self.visual_bell.update_config(&config.ui_config.bell); + self.colors = List::from(&config.ui_config.colors); + } + + /// Format search regex to account for the cursor and fullwidth characters. + fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String { + // Add spacers for wide chars. + let mut formatted_regex = String::with_capacity(search_regex.len()); + for c in search_regex.chars() { + formatted_regex.push(c); + if c.width() == Some(2) { + formatted_regex.push(' '); + } + } + + // Add cursor to show whitespace. + formatted_regex.push('_'); + + // Truncate beginning of the search regex if it exceeds the viewport width. + let num_cols = size_info.cols().0; + let label_len = search_label.chars().count(); + let regex_len = formatted_regex.chars().count(); + let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len); + let index = formatted_regex.char_indices().nth(truncate_len).map(|(i, _c)| i).unwrap_or(0); + let truncated_regex = &formatted_regex[index..]; + + // Add search label to the beginning of the search regex. + let mut bar_text = format!("{}{}", search_label, truncated_regex); + + // Make sure the label alone doesn't exceed the viewport width. + bar_text.truncate(num_cols); + + bar_text + } + + /// Draw current search regex. + fn draw_search(&mut self, config: &Config, size_info: &SizeInfo, text: &str) { + let glyph_cache = &mut self.glyph_cache; + let num_cols = size_info.cols().0; + + // Assure text length is at least num_cols. + let text = format!("{:<1$}", text, num_cols); + + let point = Point::new(size_info.screen_lines(), Column(0)); + let fg = config.ui_config.colors.search_bar_foreground(); + let bg = config.ui_config.colors.search_bar_background(); + + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + api.render_string(glyph_cache, point, fg, bg, &text); + }); + } + + /// Draw render timer. + fn draw_render_timer(&mut self, config: &Config, size_info: &SizeInfo) { + if !config.ui_config.debug.render_timer { + return; + } + + let glyph_cache = &mut self.glyph_cache; + + let timing = format!("{:.3} usec", self.meter.average()); + let point = Point::new(size_info.screen_lines() - 2, Column(0)); + let fg = config.ui_config.colors.primary.background; + let bg = config.ui_config.colors.normal.red; + + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + api.render_string(glyph_cache, point, fg, bg, &timing); + }); + } + + /// Draw an indicator for the position of a line in history. + fn draw_line_indicator( + &mut self, + config: &Config, + size_info: &SizeInfo, + total_lines: usize, + vi_mode_point: Option<Point>, + line: usize, + ) { + let text = format!("[{}/{}]", line, total_lines - 1); + let column = Column(size_info.cols().0.saturating_sub(text.len())); + let colors = &config.ui_config.colors; + let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background); + let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground); + + // Do not render anything if it would obscure the vi mode cursor. + if vi_mode_point.map_or(true, |point| point.line.0 != 0 || point.column < column) { + let glyph_cache = &mut self.glyph_cache; + self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + api.render_string(glyph_cache, Point::new(Line(0), column), fg, bg, &text); + }); + } + } + + /// Requst a new frame for a window on Wayland. + #[inline] + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + fn request_frame(&self, window: &Window) { + let surface = match window.wayland_surface() { + Some(surface) => surface, + None => return, + }; + + let should_draw = self.window.should_draw.clone(); + + // Mark that window was drawn. + should_draw.store(false, Ordering::Relaxed); + + // Request a new frame. + surface.frame().quick_assign(move |_, _, _| { + should_draw.store(true, Ordering::Relaxed); + }); + } +} + +/// Calculate the cell dimensions based on font metrics. +/// +/// This will return a tuple of the cell width and height. +#[inline] +fn compute_cell_size(config: &Config, metrics: &crossfont::Metrics) -> (f32, f32) { + let offset_x = f64::from(config.ui_config.font.offset.x); + let offset_y = f64::from(config.ui_config.font.offset.y); + ( + (metrics.average_advance + offset_x).floor().max(1.) as f32, + (metrics.line_height + offset_y).floor().max(1.) as f32, + ) +} + +/// Calculate the size of the window given padding, terminal dimensions and cell size. +fn window_size( + config: &Config, + dimensions: Dimensions, + cell_width: f32, + cell_height: f32, + dpr: f64, +) -> PhysicalSize<u32> { + let padding = config.ui_config.window.padding(dpr); + + let grid_width = cell_width * dimensions.columns.0.max(MIN_COLS) as f32; + let grid_height = cell_height * dimensions.lines.0.max(MIN_SCREEN_LINES) as f32; + + let width = (padding.0).mul_add(2., grid_width).floor(); + let height = (padding.1).mul_add(2., grid_height).floor(); + + PhysicalSize::new(width as u32, height as u32) +} |