diff options
Diffstat (limited to 'alacritty_terminal/src/display.rs')
-rw-r--r-- | alacritty_terminal/src/display.rs | 603 |
1 files changed, 0 insertions, 603 deletions
diff --git a/alacritty_terminal/src/display.rs b/alacritty_terminal/src/display.rs deleted file mode 100644 index bdd34460..00000000 --- a/alacritty_terminal/src/display.rs +++ /dev/null @@ -1,603 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! The display subsystem including window management, font rasterization, and -//! GPU drawing. -use std::f64; -#[cfg(not(any(target_os = "macos", target_os = "windows")))] -use std::ffi::c_void; -use std::sync::mpsc; - -use glutin::dpi::{PhysicalPosition, PhysicalSize}; -use glutin::EventsLoop; -use parking_lot::MutexGuard; - -use crate::config::{Config, StartupMode}; -use crate::index::Line; -use crate::message_bar::Message; -use crate::meter::Meter; -use crate::renderer::rects::{RenderLines, RenderRect}; -use crate::renderer::{self, GlyphCache, QuadRenderer}; -use crate::sync::FairMutex; -use crate::term::color::Rgb; -use crate::term::{RenderableCell, SizeInfo, Term}; -use crate::window::{self, Window}; -use font::{self, Rasterize}; - -#[derive(Debug)] -pub enum Error { - /// Error with window management - Window(window::Error), - - /// Error dealing with fonts - Font(font::Error), - - /// Error in renderer - Render(renderer::Error), -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::Window(ref err) => Some(err), - Error::Font(ref err) => Some(err), - Error::Render(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::Window(ref err) => err.description(), - Error::Font(ref err) => err.description(), - Error::Render(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::Window(ref err) => err.fmt(f), - Error::Font(ref err) => err.fmt(f), - Error::Render(ref err) => err.fmt(f), - } - } -} - -impl From<window::Error> for Error { - fn from(val: window::Error) -> Error { - Error::Window(val) - } -} - -impl From<font::Error> for Error { - fn from(val: font::Error) -> Error { - Error::Font(val) - } -} - -impl From<renderer::Error> for Error { - fn from(val: renderer::Error) -> Error { - Error::Render(val) - } -} - -/// The display wraps a window, font rasterizer, and GPU renderer -pub struct Display { - window: Window, - renderer: QuadRenderer, - glyph_cache: GlyphCache, - render_timer: bool, - rx: mpsc::Receiver<PhysicalSize>, - tx: mpsc::Sender<PhysicalSize>, - meter: Meter, - font_size: font::Size, - size_info: SizeInfo, - last_message: Option<Message>, -} - -/// Can wakeup the render loop from other threads -pub struct Notifier(window::Proxy); - -/// Types that are interested in when the display is resized -pub trait OnResize { - fn on_resize(&mut self, size: &SizeInfo); -} - -impl Notifier { - pub fn notify(&self) { - self.0.wakeup_event_loop(); - } -} - -impl Display { - pub fn notifier(&self) -> Notifier { - Notifier(self.window.create_window_proxy()) - } - - pub fn update_config(&mut self, config: &Config) { - self.render_timer = config.render_timer(); - } - - /// Get size info about the display - pub fn size(&self) -> &SizeInfo { - &self.size_info - } - - pub fn new(config: &Config) -> Result<Display, Error> { - // Extract some properties from config - let render_timer = config.render_timer(); - - // Guess DPR based on first monitor - let event_loop = EventsLoop::new(); - let estimated_dpr = - event_loop.get_available_monitors().next().map(|m| m.get_hidpi_factor()).unwrap_or(1.); - - // Guess the target window dimensions - let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?; - let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics); - let dimensions = Self::calculate_dimensions(config, estimated_dpr, cell_width, cell_height); - - debug!("Estimated DPR: {}", estimated_dpr); - debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); - debug!("Estimated Dimensions: {:?}", dimensions); - - // Create the window where Alacritty will be displayed - let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr)); - let mut window = Window::new(event_loop, &config, logical)?; - - let dpr = window.hidpi_factor(); - info!("Device pixel ratio: {}", dpr); - - // get window properties for initializing the other subsystems - let mut viewport_size = - window.inner_size_pixels().expect("glutin returns window size").to_physical(dpr); - - // Create renderer - let mut renderer = QuadRenderer::new()?; - - let (glyph_cache, cell_width, cell_height) = - Self::new_glyph_cache(dpr, &mut renderer, config)?; - - let mut padding_x = f64::from(config.window.padding.x) * dpr; - let mut padding_y = f64::from(config.window.padding.y) * dpr; - - if let Some((width, height)) = - Self::calculate_dimensions(config, dpr, cell_width, cell_height) - { - if dimensions == Some((width, height)) { - info!("Estimated DPR correctly, skipping resize"); - } else { - viewport_size = PhysicalSize::new(width, height); - window.set_inner_size(viewport_size.to_logical(dpr)); - } - } else if config.window.dynamic_padding { - // Make sure additional padding is spread evenly - let cw = f64::from(cell_width); - let ch = f64::from(cell_height); - padding_x = padding_x + (viewport_size.width - 2. * padding_x) % cw / 2.; - padding_y = padding_y + (viewport_size.height - 2. * padding_y) % ch / 2.; - } - - padding_x = padding_x.floor(); - padding_y = padding_y.floor(); - - // Update OpenGL projection - renderer.resize(viewport_size, padding_x as f32, padding_y as f32); - - info!("Cell Size: {} x {}", cell_width, cell_height); - info!("Padding: {} x {}", padding_x, padding_y); - - let size_info = SizeInfo { - dpr, - width: viewport_size.width as f32, - height: viewport_size.height as f32, - cell_width: cell_width as f32, - cell_height: cell_height as f32, - padding_x: padding_x as f32, - padding_y: padding_y as f32, - }; - - // Channel for resize events - // - // macOS has a callback for getting resize events, the channel is used - // to queue resize events until the next draw call. Unfortunately, it - // seems that the event loop is blocked until the window is done - // resizing. If any drawing were to happen during a resize, it would - // need to be in the callback. - let (tx, rx) = mpsc::channel(); - - // Clear screen - let background_color = config.colors.primary.background; - renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - // We should call `clear` when window is offscreen, so when `window.show()` happens it - // would be with background color instead of uninitialized surface. - window.swap_buffers()?; - - window.show(); - - // Set window position - // - // TODO: replace `set_position` with `with_position` once available - // Upstream issue: https://github.com/tomaka/winit/issues/806 - if let Some(position) = config.window.position { - let physical = PhysicalPosition::from((position.x, position.y)); - let logical = physical.to_logical(window.hidpi_factor()); - window.set_position(logical); - } - - #[allow(clippy::single_match)] - match config.window.startup_mode() { - StartupMode::Fullscreen => window.set_fullscreen(true), - #[cfg(target_os = "macos")] - StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), - #[cfg(not(any(target_os = "macos", windows)))] - StartupMode::Maximized if window.is_x11() => window.set_maximized(true), - _ => (), - } - - Ok(Display { - window, - renderer, - glyph_cache, - render_timer, - tx, - rx, - meter: Meter::new(), - font_size: config.font.size, - size_info, - last_message: None, - }) - } - - fn calculate_dimensions( - config: &Config, - dpr: f64, - cell_width: f32, - cell_height: f32, - ) -> Option<(f64, f64)> { - let dimensions = config.window.dimensions; - - if dimensions.columns_u32() == 0 - || dimensions.lines_u32() == 0 - || config.window.startup_mode() != StartupMode::Windowed - { - return None; - } - - let padding_x = f64::from(config.window.padding.x) * dpr; - let padding_y = f64::from(config.window.padding.y) * dpr; - - // Calculate new size based on cols/lines specified in config - let grid_width = cell_width as u32 * dimensions.columns_u32(); - let grid_height = cell_height as u32 * dimensions.lines_u32(); - - let width = (f64::from(grid_width) + 2. * padding_x).floor(); - let height = (f64::from(grid_height) + 2. * padding_y).floor(); - - Some((width, height)) - } - - fn new_glyph_cache( - dpr: f64, - renderer: &mut QuadRenderer, - config: &Config, - ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.font.clone(); - let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?; - - // Initialize glyph cache - let glyph_cache = { - info!("Initializing glyph cache..."); - let init_start = ::std::time::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) = Self::compute_cell_size(config, &glyph_cache.font_metrics()); - - Ok((glyph_cache, cw, ch)) - } - - pub fn update_glyph_cache(&mut self, config: &Config) { - let cache = &mut self.glyph_cache; - let dpr = self.size_info.dpr; - let size = self.font_size; - - self.renderer.with_loader(|mut api| { - let _ = cache.update_font_size(&config.font, size, dpr, &mut api); - }); - - let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics()); - self.size_info.cell_width = cw; - self.size_info.cell_height = ch; - } - - fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.font.offset.x); - let offset_y = f64::from(config.font.offset.y); - ( - f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()), - f32::max(1., ((metrics.line_height + offset_y) as f32).floor()), - ) - } - - #[inline] - pub fn resize_channel(&self) -> mpsc::Sender<PhysicalSize> { - self.tx.clone() - } - - pub fn window(&mut self) -> &mut Window { - &mut self.window - } - - /// Process pending resize events - pub fn handle_resize( - &mut self, - terminal: &mut MutexGuard<'_, Term>, - config: &Config, - pty_resize_handle: &mut dyn OnResize, - processor_resize_handle: &mut dyn OnResize, - ) { - let previous_cols = self.size_info.cols(); - let previous_lines = self.size_info.lines(); - - // Resize events new_size and are handled outside the poll_events - // iterator. This has the effect of coalescing multiple resize - // events into one. - let mut new_size = None; - - // Take most recent resize event, if any - while let Ok(size) = self.rx.try_recv() { - new_size = Some(size); - } - - // Update the DPR - let dpr = self.window.hidpi_factor(); - - // Font size/DPI factor modification detected - let font_changed = - terminal.font_size != self.font_size || (dpr - self.size_info.dpr).abs() > f64::EPSILON; - - // Skip resize if nothing changed - if let Some(new_size) = new_size { - if !font_changed - && (new_size.width - f64::from(self.size_info.width)).abs() < f64::EPSILON - && (new_size.height - f64::from(self.size_info.height)).abs() < f64::EPSILON - { - return; - } - } - - // Message bar update detected - let message_bar_changed = self.last_message != terminal.message_buffer_mut().message(); - - if font_changed || message_bar_changed { - if new_size == None { - // Force a resize to refresh things - new_size = Some(PhysicalSize::new( - f64::from(self.size_info.width) / self.size_info.dpr * dpr, - f64::from(self.size_info.height) / self.size_info.dpr * dpr, - )); - } - - self.font_size = terminal.font_size; - self.last_message = terminal.message_buffer_mut().message(); - self.size_info.dpr = dpr; - } - - if font_changed { - self.update_glyph_cache(config); - } - - if let Some(psize) = new_size.take() { - let width = psize.width as f32; - let height = psize.height as f32; - let cell_width = self.size_info.cell_width; - let cell_height = self.size_info.cell_height; - - self.size_info.width = width; - self.size_info.height = height; - - let mut padding_x = f32::from(config.window.padding.x) * dpr as f32; - let mut padding_y = f32::from(config.window.padding.y) * dpr as f32; - - if config.window.dynamic_padding { - padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.; - padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.; - } - - self.size_info.padding_x = padding_x.floor(); - self.size_info.padding_y = padding_y.floor(); - - let size = &self.size_info; - terminal.resize(size); - processor_resize_handle.on_resize(size); - - // Subtract message bar lines for pty size - let mut pty_size = *size; - if let Some(message) = terminal.message_buffer_mut().message() { - pty_size.height -= pty_size.cell_height * message.text(&size).len() as f32; - } - - if message_bar_changed - || previous_cols != pty_size.cols() - || previous_lines != pty_size.lines() - { - pty_resize_handle.on_resize(&pty_size); - } - - self.window.resize(psize); - self.renderer.resize(psize, self.size_info.padding_x, self.size_info.padding_y); - } - } - - /// 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(&mut self, terminal: &FairMutex<Term>, config: &Config) { - let mut terminal = terminal.lock(); - let size_info = *terminal.size_info(); - let visual_bell_intensity = terminal.visual_bell.intensity(); - let background_color = terminal.background_color(); - let metrics = self.glyph_cache.font_metrics(); - - let window_focused = self.window.is_focused; - let grid_cells: Vec<RenderableCell> = - terminal.renderable_cells(config, window_focused).collect(); - - // Get message from terminal to ignore modifications after lock is dropped - let message_buffer = terminal.message_buffer_mut().message(); - - // Clear dirty flag - terminal.dirty = !terminal.visual_bell.completed(); - - if let Some(title) = terminal.get_next_title() { - self.window.set_title(&title); - } - - if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() { - self.window.set_mouse_cursor(mouse_cursor); - } - - if let Some(is_urgent) = terminal.next_is_urgent.take() { - // We don't need to set the urgent flag if we already have the - // user's attention. - if !is_urgent || !self.window.is_focused { - self.window.set_urgent(is_urgent); - } - } - - // Clear when terminal mutex isn't held. Mesa for - // some reason takes a long time to call glClear(). The driver descends - // into xcb_connect_to_fd() which ends up calling __poll_nocancel() - // which blocks for a while. - // - // By keeping this outside of the critical region, the Mesa bug is - // worked around to some extent. Since this doesn't actually address the - // issue of glClear being slow, less time is available for input - // handling and rendering. - drop(terminal); - - self.renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - { - let glyph_cache = &mut self.glyph_cache; - let mut lines = RenderLines::new(); - - // Draw grid - { - let _sampler = self.meter.sampler(); - - self.renderer.with_api(config, &size_info, |mut api| { - // Iterate over all non-empty cells in the grid - for cell in grid_cells { - // Update underline/strikeout - lines.update(&cell); - - // Draw the cell - api.render_cell(cell, glyph_cache); - } - }); - } - - let mut rects = lines.into_rects(&metrics, &size_info); - - if let Some(message) = message_buffer { - let text = message.text(&size_info); - - // Create a new rectangle for the background - let start_line = size_info.lines().0 - text.len(); - let y = size_info.padding_y + size_info.cell_height * start_line as f32; - rects.push(RenderRect::new( - 0., - y, - size_info.width, - size_info.height - y, - message.color(), - )); - - // Draw rectangles including the new background - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - - // Relay messages to the user - let mut offset = 1; - for message_text in text.iter().rev() { - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string( - &message_text, - Line(size_info.lines().saturating_sub(offset)), - glyph_cache, - None, - ); - }); - offset += 1; - } - } else { - // Draw rectangles - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - } - - // Draw render timer - if self.render_timer { - let timing = format!("{:.3} usec", self.meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color)); - }); - } - } - - self.window.swap_buffers().expect("swap buffers"); - } - - pub fn get_window_id(&self) -> Option<usize> { - self.window.get_window_id() - } - - /// Adjust the IME editor position according to the new location of the cursor - pub fn update_ime_position(&mut self, terminal: &Term) { - let point = terminal.cursor().point; - let SizeInfo { cell_width: cw, cell_height: ch, padding_x: px, padding_y: py, .. } = - *terminal.size_info(); - - let dpr = self.window().hidpi_factor(); - let nspot_x = f64::from(px + point.col.0 as f32 * cw); - let nspot_y = f64::from(py + (point.line.0 + 1) as f32 * ch); - - self.window().set_ime_spot(PhysicalPosition::from((nspot_x, nspot_y)).to_logical(dpr)); - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn get_wayland_display(&self) -> Option<*mut c_void> { - self.window.get_wayland_display() - } -} |