From 39af18d647b4aeff43d6829f43fe4e57628691c0 Mon Sep 17 00:00:00 2001 From: fredizzimo Date: Mon, 24 Jul 2023 15:02:04 +0300 Subject: [PATCH] Extract window_wrapper and the update loop to their own modules (#1939) --- src/renderer/mod.rs | 2 +- src/renderer/opengl.rs | 13 +- src/window/mod.rs | 494 ++--------------------------------- src/window/update_loop.rs | 117 +++++++++ src/window/window_wrapper.rs | 427 ++++++++++++++++++++++++++++++ 5 files changed, 573 insertions(+), 480 deletions(-) create mode 100644 src/window/update_loop.rs create mode 100644 src/window/window_wrapper.rs diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index f6eb9456..470eebac 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -33,7 +33,7 @@ pub use rendered_window::{ LineFragment, RenderedWindow, WindowDrawCommand, WindowDrawDetails, WindowPadding, }; -pub use opengl::{build_context, Context as WindowedContext}; +pub use opengl::{build_context, build_window, Context as WindowedContext}; #[derive(SettingGroup, Clone)] pub struct RendererSettings { diff --git a/src/renderer/opengl.rs b/src/renderer/opengl.rs index 612d700f..383c3e23 100644 --- a/src/renderer/opengl.rs +++ b/src/renderer/opengl.rs @@ -60,11 +60,10 @@ fn gen_config(mut config_iterator: Box + '_>) -> Con config_iterator.next().unwrap() } -pub fn build_context( - cmd_line_settings: &CmdLineSettings, +pub fn build_window( winit_window_builder: WindowBuilder, event_loop: &EventLoop, -) -> Context { +) -> (Window, Config) { let template_builder = ConfigTemplateBuilder::new() .with_stencil_size(8) .with_transparency(true); @@ -72,8 +71,14 @@ pub fn build_context( .with_window_builder(Some(winit_window_builder)) .build(event_loop, template_builder, gen_config) .expect("Failed to create Window"); - let window = window.expect("Could not create Window"); + (window.expect("Could not create Window"), config) +} +pub fn build_context( + window: Window, + config: Config, + cmd_line_settings: &CmdLineSettings, +) -> Context { let gl_display = config.display(); let raw_window_handle = window.raw_window_handle(); diff --git a/src/window/mod.rs b/src/window/mod.rs index b897b820..6b4e0b30 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -2,21 +2,18 @@ mod keyboard_manager; mod mouse_manager; mod renderer; mod settings; +mod update_loop; +mod window_wrapper; #[cfg(target_os = "macos")] mod draw_background; #[cfg(target_os = "linux")] use std::env; -use std::time::{Duration, Instant}; -use log::trace; -use tokio::sync::mpsc::UnboundedReceiver; use winit::{ - dpi::{PhysicalPosition, PhysicalSize, Position}, - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{self, Fullscreen, Icon, Theme}, + event_loop::EventLoop, + window::{Icon, WindowBuilder}, }; #[cfg(target_os = "macos")] @@ -34,34 +31,20 @@ use image::{load_from_memory, GenericImageView, Pixel}; use keyboard_manager::KeyboardManager; use mouse_manager::MouseManager; use renderer::SkiaRenderer; +use update_loop::UpdateLoop; +use window_wrapper::WinitWindowWrapper; use crate::{ - bridge::{ParallelCommand, UiCommand}, cmd_line::CmdLineSettings, - dimensions::Dimensions, - editor::EditorCommand, - event_aggregator::EVENT_AGGREGATOR, frame::Frame, - profiling::{ - emit_frame_mark, tracy_create_gpu_context, tracy_gpu_collect, tracy_gpu_zone, tracy_zone, - }, - redraw_scheduler::REDRAW_SCHEDULER, - renderer::Renderer, - renderer::WindowPadding, - renderer::{build_context, WindowedContext}, - running_tracker::*, - settings::{ - load_last_window_settings, save_window_size, PersistentWindowSettings, - DEFAULT_WINDOW_GEOMETRY, SETTINGS, - }, + profiling::tracy_create_gpu_context, + renderer::build_window, + settings::{load_last_window_settings, PersistentWindowSettings, SETTINGS}, }; pub use settings::{KeyboardSettings, WindowSettings}; static ICON: &[u8] = include_bytes!("../../assets/neovide.ico"); -const MIN_WINDOW_WIDTH: u64 = 20; -const MIN_WINDOW_HEIGHT: u64 = 6; - #[derive(Clone, Debug)] pub enum WindowCommand { TitleChanged(String), @@ -69,313 +52,6 @@ pub enum WindowCommand { ListAvailableFonts, } -pub struct WinitWindowWrapper { - windowed_context: WindowedContext, - skia_renderer: SkiaRenderer, - renderer: Renderer, - keyboard_manager: KeyboardManager, - mouse_manager: MouseManager, - title: String, - fullscreen: bool, - font_changed_last_frame: bool, - saved_inner_size: PhysicalSize, - saved_grid_size: Option, - size_at_startup: PhysicalSize, - maximized_at_startup: bool, - window_command_receiver: UnboundedReceiver, - ime_enabled: bool, -} - -pub fn set_background(background: &str) { - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::SetBackground( - background.to_string(), - ))); -} - -impl WinitWindowWrapper { - pub fn toggle_fullscreen(&mut self) { - let window = self.windowed_context.window(); - if self.fullscreen { - window.set_fullscreen(None); - } else { - let handle = window.current_monitor(); - window.set_fullscreen(Some(Fullscreen::Borderless(handle))); - } - - self.fullscreen = !self.fullscreen; - } - - pub fn set_ime(&mut self, ime_enabled: bool) { - self.ime_enabled = ime_enabled; - self.windowed_context.window().set_ime_allowed(ime_enabled); - } - - pub fn synchronize_settings(&mut self) { - let fullscreen = { SETTINGS.get::().fullscreen }; - - if self.fullscreen != fullscreen { - self.toggle_fullscreen(); - } - - let ime_enabled = { SETTINGS.get::().ime }; - - if self.ime_enabled != ime_enabled { - self.set_ime(ime_enabled); - } - } - - #[allow(clippy::needless_collect)] - pub fn handle_window_commands(&mut self) { - tracy_zone!("handle_window_commands", 0); - while let Ok(window_command) = self.window_command_receiver.try_recv() { - match window_command { - WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title), - WindowCommand::SetMouseEnabled(mouse_enabled) => { - self.mouse_manager.enabled = mouse_enabled - } - WindowCommand::ListAvailableFonts => self.send_font_names(), - } - } - } - - pub fn handle_title_changed(&mut self, new_title: String) { - self.title = new_title; - self.windowed_context.window().set_title(&self.title); - } - - pub fn send_font_names(&self) { - let font_names = self.renderer.font_names(); - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::DisplayAvailableFonts( - font_names, - ))); - } - - pub fn handle_quit(&mut self) { - if SETTINGS.get::().server.is_none() { - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Quit)); - } else { - RUNNING_TRACKER.quit("window closed"); - } - } - - pub fn handle_focus_lost(&mut self) { - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusLost)); - } - - pub fn handle_focus_gained(&mut self) { - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusGained)); - REDRAW_SCHEDULER.queue_next_frame(); - } - - pub fn handle_event(&mut self, event: Event<()>) { - tracy_zone!("handle_event", 0); - self.keyboard_manager.handle_event(&event); - self.mouse_manager.handle_event( - &event, - &self.keyboard_manager, - &self.renderer, - self.windowed_context.window(), - ); - self.renderer.handle_event(&event); - match event { - Event::LoopDestroyed => { - self.handle_quit(); - } - Event::Resumed => { - EVENT_AGGREGATOR.send(EditorCommand::RedrawScreen); - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - self.handle_quit(); - } - Event::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, - .. - } => { - self.handle_scale_factor_update(scale_factor); - } - Event::WindowEvent { - event: WindowEvent::DroppedFile(path), - .. - } => { - let file_path = path.into_os_string().into_string().unwrap(); - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FileDrop(file_path))); - } - Event::WindowEvent { - event: WindowEvent::Focused(focus), - .. - } => { - if focus { - self.handle_focus_gained(); - } else { - self.handle_focus_lost(); - } - } - Event::WindowEvent { - event: WindowEvent::ThemeChanged(theme), - .. - } => { - let settings = SETTINGS.get::(); - if settings.theme.as_str() == "auto" { - let background = match theme { - Theme::Light => "light", - Theme::Dark => "dark", - }; - set_background(background); - } - } - Event::RedrawRequested(..) | Event::WindowEvent { .. } => { - REDRAW_SCHEDULER.queue_next_frame() - } - _ => {} - } - } - - pub fn draw_frame(&mut self, dt: f32) { - tracy_zone!("draw_frame"); - let window = self.windowed_context.window(); - - let window_settings = SETTINGS.get::(); - let window_padding = WindowPadding { - top: window_settings.padding_top, - left: window_settings.padding_left, - right: window_settings.padding_right, - bottom: window_settings.padding_bottom, - }; - - let padding_changed = window_padding != self.renderer.window_padding; - if padding_changed { - self.renderer.window_padding = window_padding; - } - - let new_size = window.inner_size(); - if self.saved_inner_size != new_size || self.font_changed_last_frame || padding_changed { - self.font_changed_last_frame = false; - self.saved_inner_size = new_size; - - self.handle_new_grid_size(new_size); - self.skia_renderer.resize(&self.windowed_context); - } - - if REDRAW_SCHEDULER.should_draw() || !SETTINGS.get::().idle { - let prev_cursor_position = self.renderer.get_cursor_position(); - self.font_changed_last_frame = - self.renderer.draw_frame(self.skia_renderer.canvas(), dt); - { - tracy_gpu_zone!("skia flush"); - self.skia_renderer.gr_context.flush(None); - } - { - tracy_gpu_zone!("swap buffers"); - self.windowed_context.swap_buffers().unwrap(); - } - emit_frame_mark(); - tracy_gpu_collect(); - let current_cursor_position = self.renderer.get_cursor_position(); - if current_cursor_position != prev_cursor_position { - let font_dimensions = self.renderer.grid_renderer.font_dimensions; - let position = PhysicalPosition::new( - current_cursor_position.x.round() as i32, - current_cursor_position.y.round() as i32 + font_dimensions.height as i32, - ); - self.windowed_context.window().set_ime_cursor_area( - Position::Physical(position), - PhysicalSize::new(100, font_dimensions.height as u32), - ); - } - } - - // Wait until fonts are loaded, so we can set proper window size. - if !self.renderer.grid_renderer.is_ready { - return; - } - - // Resize at startup happens when window is maximized or when using tiling WM - // which already resized window. - let resized_at_startup = self.maximized_at_startup || self.has_been_resized(); - - log::trace!("Inner size: {:?}", new_size); - - if self.saved_grid_size.is_none() && !resized_at_startup { - self.init_window_size(); - } - } - - fn init_window_size(&self) { - let settings = SETTINGS.get::(); - log::trace!("Settings geometry {:?}", settings.geometry,); - log::trace!("Settings size {:?}", settings.size); - - let window = self.windowed_context.window(); - let inner_size = if let Some(size) = settings.size { - // --size - size.into() - } else if let Some(geometry) = settings.geometry { - // --geometry - self.renderer - .grid_renderer - .convert_grid_to_physical(geometry) - } else if let Ok(PersistentWindowSettings::Windowed { - pixel_size: Some(size), - .. - }) = load_last_window_settings() - { - // remembered size - size - } else { - // default geometry - self.renderer - .grid_renderer - .convert_grid_to_physical(DEFAULT_WINDOW_GEOMETRY) - }; - window.set_inner_size(inner_size); - // next frame will detect change in window.inner_size() and hence will - // handle_new_grid_size automatically - } - - fn handle_new_grid_size(&mut self, new_size: PhysicalSize) { - let window_padding = self.renderer.window_padding; - let window_padding_width = window_padding.left + window_padding.right; - let window_padding_height = window_padding.top + window_padding.bottom; - - let content_size = PhysicalSize { - width: new_size.width - window_padding_width, - height: new_size.height - window_padding_height, - }; - - let grid_size = self - .renderer - .grid_renderer - .convert_physical_to_grid(content_size); - - // Have a minimum size - if grid_size.width < MIN_WINDOW_WIDTH || grid_size.height < MIN_WINDOW_HEIGHT { - return; - } - - if self.saved_grid_size == Some(grid_size) { - trace!("Grid matched saved size, skip update."); - return; - } - self.saved_grid_size = Some(grid_size); - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Resize { - width: grid_size.width, - height: grid_size.height, - })); - } - - fn handle_scale_factor_update(&mut self, scale_factor: f64) { - self.renderer.handle_os_scale_factor_change(scale_factor); - EVENT_AGGREGATOR.send(EditorCommand::RedrawScreen); - } - - fn has_been_resized(&self) -> bool { - self.windowed_context.window().inner_size() != self.size_at_startup - } -} - pub fn create_window() { let icon = { let icon = load_from_memory(ICON).expect("Failed to parse icon data"); @@ -404,7 +80,7 @@ pub fn create_window() { } } - let winit_window_builder = window::WindowBuilder::new() + let winit_window_builder = WindowBuilder::new() .with_title("Neovide") .with_window_icon(Some(icon)) .with_maximized(maximized) @@ -454,152 +130,20 @@ pub fn create_window() { #[cfg(target_os = "macos")] let winit_window_builder = winit_window_builder.with_accepts_first_mouse(false); - let windowed_context = build_context(&cmd_line_settings, winit_window_builder, &event_loop); - - let window = windowed_context.window(); - let initial_size = window.inner_size(); - - // Check that window is visible in some monitor, and reposition it if not. - let did_reposition = window - .current_monitor() - .and_then(|current_monitor| { - let monitor_position = current_monitor.position(); - let monitor_size = current_monitor.size(); - let monitor_width = monitor_size.width as i32; - let monitor_height = monitor_size.height as i32; - - let window_position = previous_position - .filter(|_| !maximized) - .or_else(|| window.outer_position().ok())?; - - let window_size = window.outer_size(); - let window_width = window_size.width as i32; - let window_height = window_size.height as i32; - - if window_position.x + window_width < monitor_position.x - || window_position.y + window_height < monitor_position.y - || window_position.x > monitor_position.x + monitor_width - || window_position.y > monitor_position.y + monitor_height - { - window.set_outer_position(monitor_position); - } - - Some(()) - }) - .is_some(); - - log::trace!("repositioned window: {}", did_reposition); - - let scale_factor = windowed_context.window().scale_factor(); - let renderer = Renderer::new(scale_factor); - let saved_inner_size = window.inner_size(); - - let skia_renderer = SkiaRenderer::new(&windowed_context); - - let window_command_receiver = EVENT_AGGREGATOR.register_event::(); - - log::info!( - "window created (scale_factor: {:.4}, font_dimensions: {:?})", - scale_factor, - renderer.grid_renderer.font_dimensions, + let (window, config) = build_window(winit_window_builder, &event_loop); + let mut window_wrapper = WinitWindowWrapper::new( + window, + config, + &cmd_line_settings, + previous_position, + maximized, ); - let ime_enabled = { SETTINGS.get::().ime }; - - match SETTINGS.get::().theme.as_str() { - "light" => set_background("light"), - "dark" => set_background("dark"), - "auto" => match window.theme() { - Some(Theme::Light) => set_background("light"), - Some(Theme::Dark) => set_background("dark"), - None => {} - }, - _ => {} - } - - let mut window_wrapper = WinitWindowWrapper { - windowed_context, - skia_renderer, - renderer, - keyboard_manager: KeyboardManager::new(), - mouse_manager: MouseManager::new(), - title: String::from("Neovide"), - fullscreen: false, - font_changed_last_frame: false, - size_at_startup: initial_size, - maximized_at_startup: maximized, - saved_inner_size, - saved_grid_size: None, - window_command_receiver, - ime_enabled, - }; - - window_wrapper.set_ime(ime_enabled); - tracy_create_gpu_context("main_render_context"); - let mut previous_frame_start = Instant::now(); - - enum FocusedState { - Focused, - UnfocusedNotDrawn, - Unfocused, - } - let mut focused = FocusedState::Focused; + let mut update_loop = UpdateLoop::new(); event_loop.run(move |e, _window_target, control_flow| { - // Window focus changed - if let Event::WindowEvent { - event: WindowEvent::Focused(focused_event), - .. - } = e - { - focused = if focused_event { - FocusedState::Focused - } else { - FocusedState::UnfocusedNotDrawn - }; - } - - if !RUNNING_TRACKER.is_running() { - let window = window_wrapper.windowed_context.window(); - save_window_size( - window.is_maximized(), - window.inner_size(), - window.outer_position().ok(), - ); - - std::process::exit(RUNNING_TRACKER.exit_code()); - } - - let frame_start = Instant::now(); - - window_wrapper.handle_window_commands(); - window_wrapper.synchronize_settings(); - window_wrapper.handle_event(e); - - let refresh_rate = match focused { - FocusedState::Focused | FocusedState::UnfocusedNotDrawn => { - SETTINGS.get::().refresh_rate as f32 - } - FocusedState::Unfocused => SETTINGS.get::().refresh_rate_idle as f32, - } - .max(1.0); - - let expected_frame_length_seconds = 1.0 / refresh_rate; - let frame_duration = Duration::from_secs_f32(expected_frame_length_seconds); - - if frame_start - previous_frame_start > frame_duration { - let dt = previous_frame_start.elapsed().as_secs_f32(); - window_wrapper.draw_frame(dt); - if let FocusedState::UnfocusedNotDrawn = focused { - focused = FocusedState::Unfocused; - } - previous_frame_start = frame_start; - #[cfg(target_os = "macos")] - draw_background(window_wrapper.windowed_context.window()); - } - - *control_flow = ControlFlow::WaitUntil(previous_frame_start + frame_duration) + *control_flow = update_loop.step(&mut window_wrapper, e); }); } diff --git a/src/window/update_loop.rs b/src/window/update_loop.rs new file mode 100644 index 00000000..9b7cadde --- /dev/null +++ b/src/window/update_loop.rs @@ -0,0 +1,117 @@ +use std::time::{Duration, Instant}; + +use winit::{ + event::{Event, WindowEvent}, + event_loop::ControlFlow, +}; + +#[cfg(target_os = "macos")] +use super::draw_background; +use super::{WindowSettings, WinitWindowWrapper}; +use crate::{ + profiling::{tracy_create_gpu_context, tracy_zone}, + running_tracker::*, + settings::{save_window_size, SETTINGS}, +}; + +enum FocusedState { + Focused, + UnfocusedNotDrawn, + Unfocused, +} + +pub struct UpdateLoop { + previous_frame_start: Instant, + focused: FocusedState, +} + +impl UpdateLoop { + pub fn new() -> Self { + tracy_create_gpu_context("main_render_context"); + + let previous_frame_start = Instant::now(); + let focused = FocusedState::Focused; + + Self { + previous_frame_start, + focused, + } + } + + fn get_event_deadline(&self) -> Instant { + let refresh_rate = match self.focused { + FocusedState::Focused | FocusedState::UnfocusedNotDrawn => { + SETTINGS.get::().refresh_rate as f32 + } + FocusedState::Unfocused => SETTINGS.get::().refresh_rate_idle as f32, + } + .max(1.0); + + let expected_frame_duration = Duration::from_secs_f32(1.0 / refresh_rate); + self.previous_frame_start + expected_frame_duration + } + + pub fn step( + &mut self, + window_wrapper: &mut WinitWindowWrapper, + event: Event<()>, + ) -> ControlFlow { + tracy_zone!("render loop", 0); + + // Window focus changed + if let Event::WindowEvent { + event: WindowEvent::Focused(focused_event), + .. + } = event + { + self.focused = if focused_event { + FocusedState::Focused + } else { + FocusedState::UnfocusedNotDrawn + }; + } + + let deadline = self.get_event_deadline(); + + if !RUNNING_TRACKER.is_running() { + let window = window_wrapper.windowed_context.window(); + save_window_size( + window.is_maximized(), + window.inner_size(), + window.outer_position().ok(), + ); + + std::process::exit(RUNNING_TRACKER.exit_code()); + } + + let frame_start = Instant::now(); + + window_wrapper.handle_window_commands(); + window_wrapper.synchronize_settings(); + window_wrapper.handle_event(event); + + let refresh_rate = match self.focused { + FocusedState::Focused | FocusedState::UnfocusedNotDrawn => { + SETTINGS.get::().refresh_rate as f32 + } + FocusedState::Unfocused => SETTINGS.get::().refresh_rate_idle as f32, + } + .max(0.0); + + let expected_frame_length_seconds = 1.0 / refresh_rate; + let frame_duration = Duration::from_secs_f32(expected_frame_length_seconds); + + if frame_start - self.previous_frame_start > frame_duration { + let dt = self.previous_frame_start.elapsed().as_secs_f32(); + window_wrapper.draw_frame(dt); + if let FocusedState::UnfocusedNotDrawn = self.focused { + self.focused = FocusedState::Unfocused; + } + self.previous_frame_start = frame_start; + #[cfg(target_os = "macos")] + draw_background(window_wrapper.windowed_context.window()); + } + + ControlFlow::WaitUntil(deadline) + } +} diff --git a/src/window/window_wrapper.rs b/src/window/window_wrapper.rs new file mode 100644 index 00000000..723dc809 --- /dev/null +++ b/src/window/window_wrapper.rs @@ -0,0 +1,427 @@ +use super::{ + KeyboardManager, KeyboardSettings, MouseManager, SkiaRenderer, WindowCommand, WindowSettings, +}; + +use crate::{ + bridge::{ParallelCommand, UiCommand}, + dimensions::Dimensions, + editor::EditorCommand, + event_aggregator::EVENT_AGGREGATOR, + profiling::{emit_frame_mark, tracy_gpu_collect, tracy_gpu_zone, tracy_zone}, + redraw_scheduler::REDRAW_SCHEDULER, + renderer::{build_context, Renderer, WindowPadding, WindowedContext}, + running_tracker::RUNNING_TRACKER, + settings::{ + load_last_window_settings, PersistentWindowSettings, DEFAULT_WINDOW_GEOMETRY, SETTINGS, + }, + CmdLineSettings, +}; + +use glutin::config::Config; +use log::trace; +use tokio::sync::mpsc::UnboundedReceiver; +use winit::{ + dpi::{PhysicalPosition, PhysicalSize, Position}, + event::{Event, WindowEvent}, + window::{Fullscreen, Theme, Window}, +}; + +const MIN_WINDOW_WIDTH: u64 = 20; +const MIN_WINDOW_HEIGHT: u64 = 6; + +pub struct WinitWindowWrapper { + pub windowed_context: WindowedContext, + skia_renderer: SkiaRenderer, + renderer: Renderer, + keyboard_manager: KeyboardManager, + mouse_manager: MouseManager, + title: String, + fullscreen: bool, + font_changed_last_frame: bool, + saved_inner_size: PhysicalSize, + saved_grid_size: Option, + size_at_startup: PhysicalSize, + maximized_at_startup: bool, + window_command_receiver: UnboundedReceiver, + ime_enabled: bool, +} + +pub fn set_background(background: &str) { + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::SetBackground( + background.to_string(), + ))); +} + +impl WinitWindowWrapper { + pub fn new( + window: Window, + config: Config, + cmd_line_settings: &CmdLineSettings, + previous_position: Option>, + maximized: bool, + ) -> Self { + let windowed_context = build_context(window, config, cmd_line_settings); + let window = windowed_context.window(); + let initial_size = window.inner_size(); + + // Check that window is visible in some monitor, and reposition it if not. + let did_reposition = window + .current_monitor() + .and_then(|current_monitor| { + let monitor_position = current_monitor.position(); + let monitor_size = current_monitor.size(); + let monitor_width = monitor_size.width as i32; + let monitor_height = monitor_size.height as i32; + + let window_position = previous_position + .filter(|_| !maximized) + .or_else(|| window.outer_position().ok())?; + + let window_size = window.outer_size(); + let window_width = window_size.width as i32; + let window_height = window_size.height as i32; + + if window_position.x + window_width < monitor_position.x + || window_position.y + window_height < monitor_position.y + || window_position.x > monitor_position.x + monitor_width + || window_position.y > monitor_position.y + monitor_height + { + window.set_outer_position(monitor_position); + } + + Some(()) + }) + .is_some(); + + log::trace!("repositioned window: {}", did_reposition); + + let scale_factor = windowed_context.window().scale_factor(); + let renderer = Renderer::new(scale_factor); + let saved_inner_size = window.inner_size(); + + let skia_renderer = SkiaRenderer::new(&windowed_context); + + let window_command_receiver = EVENT_AGGREGATOR.register_event::(); + + log::info!( + "window created (scale_factor: {:.4}, font_dimensions: {:?})", + scale_factor, + renderer.grid_renderer.font_dimensions, + ); + + let ime_enabled = { SETTINGS.get::().ime }; + + match SETTINGS.get::().theme.as_str() { + "light" => set_background("light"), + "dark" => set_background("dark"), + "auto" => match window.theme() { + Some(Theme::Light) => set_background("light"), + Some(Theme::Dark) => set_background("dark"), + None => {} + }, + _ => {} + } + + let mut window_wrapper = WinitWindowWrapper { + windowed_context, + skia_renderer, + renderer, + keyboard_manager: KeyboardManager::new(), + mouse_manager: MouseManager::new(), + title: String::from("Neovide"), + fullscreen: false, + font_changed_last_frame: false, + size_at_startup: initial_size, + maximized_at_startup: maximized, + saved_inner_size, + saved_grid_size: None, + window_command_receiver, + ime_enabled, + }; + + window_wrapper.set_ime(ime_enabled); + window_wrapper + } + + pub fn toggle_fullscreen(&mut self) { + let window = self.windowed_context.window(); + if self.fullscreen { + window.set_fullscreen(None); + } else { + let handle = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(handle))); + } + + self.fullscreen = !self.fullscreen; + } + + pub fn set_ime(&mut self, ime_enabled: bool) { + self.ime_enabled = ime_enabled; + self.windowed_context.window().set_ime_allowed(ime_enabled); + } + + pub fn synchronize_settings(&mut self) { + let fullscreen = { SETTINGS.get::().fullscreen }; + + if self.fullscreen != fullscreen { + self.toggle_fullscreen(); + } + + let ime_enabled = { SETTINGS.get::().ime }; + + if self.ime_enabled != ime_enabled { + self.set_ime(ime_enabled); + } + } + + #[allow(clippy::needless_collect)] + pub fn handle_window_commands(&mut self) { + tracy_zone!("handle_window_commands", 0); + while let Ok(window_command) = self.window_command_receiver.try_recv() { + match window_command { + WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title), + WindowCommand::SetMouseEnabled(mouse_enabled) => { + self.mouse_manager.enabled = mouse_enabled + } + WindowCommand::ListAvailableFonts => self.send_font_names(), + } + } + } + + pub fn handle_title_changed(&mut self, new_title: String) { + self.title = new_title; + self.windowed_context.window().set_title(&self.title); + } + + pub fn send_font_names(&self) { + let font_names = self.renderer.font_names(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::DisplayAvailableFonts( + font_names, + ))); + } + + pub fn handle_quit(&mut self) { + if SETTINGS.get::().server.is_none() { + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Quit)); + } else { + RUNNING_TRACKER.quit("window closed"); + } + } + + pub fn handle_focus_lost(&mut self) { + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusLost)); + } + + pub fn handle_focus_gained(&mut self) { + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusGained)); + REDRAW_SCHEDULER.queue_next_frame(); + } + + pub fn handle_event(&mut self, event: Event<()>) { + tracy_zone!("handle_event", 0); + self.keyboard_manager.handle_event(&event); + self.mouse_manager.handle_event( + &event, + &self.keyboard_manager, + &self.renderer, + self.windowed_context.window(), + ); + self.renderer.handle_event(&event); + match event { + Event::LoopDestroyed => { + self.handle_quit(); + } + Event::Resumed => { + EVENT_AGGREGATOR.send(EditorCommand::RedrawScreen); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + self.handle_quit(); + } + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => { + self.handle_scale_factor_update(scale_factor); + } + Event::WindowEvent { + event: WindowEvent::DroppedFile(path), + .. + } => { + let file_path = path.into_os_string().into_string().unwrap(); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FileDrop(file_path))); + } + Event::WindowEvent { + event: WindowEvent::Focused(focus), + .. + } => { + if focus { + self.handle_focus_gained(); + } else { + self.handle_focus_lost(); + } + } + Event::WindowEvent { + event: WindowEvent::ThemeChanged(theme), + .. + } => { + let settings = SETTINGS.get::(); + if settings.theme.as_str() == "auto" { + let background = match theme { + Theme::Light => "light", + Theme::Dark => "dark", + }; + set_background(background); + } + } + Event::RedrawRequested(..) | Event::WindowEvent { .. } => { + REDRAW_SCHEDULER.queue_next_frame() + } + _ => {} + } + } + + pub fn draw_frame(&mut self, dt: f32) { + tracy_zone!("draw_frame"); + let window = self.windowed_context.window(); + + let window_settings = SETTINGS.get::(); + let window_padding = WindowPadding { + top: window_settings.padding_top, + left: window_settings.padding_left, + right: window_settings.padding_right, + bottom: window_settings.padding_bottom, + }; + + let padding_changed = window_padding != self.renderer.window_padding; + if padding_changed { + self.renderer.window_padding = window_padding; + } + + let new_size = window.inner_size(); + if self.saved_inner_size != new_size || self.font_changed_last_frame || padding_changed { + self.font_changed_last_frame = false; + self.saved_inner_size = new_size; + + self.handle_new_grid_size(new_size); + self.skia_renderer.resize(&self.windowed_context); + } + + if REDRAW_SCHEDULER.should_draw() || !SETTINGS.get::().idle { + let prev_cursor_position = self.renderer.get_cursor_position(); + self.font_changed_last_frame = + self.renderer.draw_frame(self.skia_renderer.canvas(), dt); + { + tracy_gpu_zone!("skia flush"); + self.skia_renderer.gr_context.flush(None); + } + { + tracy_gpu_zone!("swap buffers"); + self.windowed_context.swap_buffers().unwrap(); + } + emit_frame_mark(); + tracy_gpu_collect(); + let current_cursor_position = self.renderer.get_cursor_position(); + if current_cursor_position != prev_cursor_position { + let font_dimensions = self.renderer.grid_renderer.font_dimensions; + let position = PhysicalPosition::new( + current_cursor_position.x.round() as i32, + current_cursor_position.y.round() as i32 + font_dimensions.height as i32, + ); + self.windowed_context.window().set_ime_cursor_area( + Position::Physical(position), + PhysicalSize::new(100, font_dimensions.height as u32), + ); + } + } + + // Wait until fonts are loaded, so we can set proper window size. + if !self.renderer.grid_renderer.is_ready { + return; + } + + // Resize at startup happens when window is maximized or when using tiling WM + // which already resized window. + let resized_at_startup = self.maximized_at_startup || self.has_been_resized(); + + log::trace!("Inner size: {:?}", new_size); + + if self.saved_grid_size.is_none() && !resized_at_startup { + self.init_window_size(); + } + } + + fn init_window_size(&self) { + let settings = SETTINGS.get::(); + log::trace!("Settings geometry {:?}", settings.geometry,); + log::trace!("Settings size {:?}", settings.size); + + let window = self.windowed_context.window(); + let inner_size = if let Some(size) = settings.size { + // --size + size.into() + } else if let Some(geometry) = settings.geometry { + // --geometry + self.renderer + .grid_renderer + .convert_grid_to_physical(geometry) + } else if let Ok(PersistentWindowSettings::Windowed { + pixel_size: Some(size), + .. + }) = load_last_window_settings() + { + // remembered size + size + } else { + // default geometry + self.renderer + .grid_renderer + .convert_grid_to_physical(DEFAULT_WINDOW_GEOMETRY) + }; + window.set_inner_size(inner_size); + // next frame will detect change in window.inner_size() and hence will + // handle_new_grid_size automatically + } + + fn handle_new_grid_size(&mut self, new_size: PhysicalSize) { + let window_padding = self.renderer.window_padding; + let window_padding_width = window_padding.left + window_padding.right; + let window_padding_height = window_padding.top + window_padding.bottom; + + let content_size = PhysicalSize { + width: new_size.width - window_padding_width, + height: new_size.height - window_padding_height, + }; + + let grid_size = self + .renderer + .grid_renderer + .convert_physical_to_grid(content_size); + + // Have a minimum size + if grid_size.width < MIN_WINDOW_WIDTH || grid_size.height < MIN_WINDOW_HEIGHT { + return; + } + + if self.saved_grid_size == Some(grid_size) { + trace!("Grid matched saved size, skip update."); + return; + } + self.saved_grid_size = Some(grid_size); + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::Resize { + width: grid_size.width, + height: grid_size.height, + })); + } + + fn handle_scale_factor_update(&mut self, scale_factor: f64) { + self.renderer.handle_os_scale_factor_change(scale_factor); + EVENT_AGGREGATOR.send(EditorCommand::RedrawScreen); + } + + fn has_been_resized(&self) -> bool { + self.windowed_context.window().inner_size() != self.size_at_startup + } +}