From 6b96f79bb19abd86eb097e802734e784aed6f47f Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Sun, 12 Dec 2021 23:36:34 -0800 Subject: [PATCH] move window files into main window directory rather than being window wrapper --- src/main.rs | 1 - src/renderer/grid_renderer.rs | 5 +- .../{window_wrapper => }/keyboard_manager.rs | 0 src/window/mod.rs | 352 +++++++++++++++++- .../{window_wrapper => }/mouse_manager.rs | 0 src/window/{window_wrapper => }/renderer.rs | 0 src/window/window_wrapper/mod.rs | 349 ----------------- 7 files changed, 352 insertions(+), 355 deletions(-) rename src/window/{window_wrapper => }/keyboard_manager.rs (100%) rename src/window/{window_wrapper => }/mouse_manager.rs (100%) rename src/window/{window_wrapper => }/renderer.rs (100%) delete mode 100644 src/window/window_wrapper/mod.rs diff --git a/src/main.rs b/src/main.rs index c4dd4c6..a061607 100644 --- a/src/main.rs +++ b/src/main.rs @@ -256,4 +256,3 @@ fn windows_attach_to_console() { AttachConsole(ATTACH_PARENT_PROCESS); } } - diff --git a/src/renderer/grid_renderer.rs b/src/renderer/grid_renderer.rs index e7fd39a..4a141f8 100644 --- a/src/renderer/grid_renderer.rs +++ b/src/renderer/grid_renderer.rs @@ -100,8 +100,9 @@ impl GridRenderer { .set_color(style.background(&self.default_style.colors).to_color()); } - if (SETTINGS.get::().transparency - 1.0).abs() > f32::EPSILON && - self.paint.color() == self.get_default_background() { + if (SETTINGS.get::().transparency - 1.0).abs() > f32::EPSILON + && self.paint.color() == self.get_default_background() + { if is_floating { self.paint .set_alpha((255.0 * SETTINGS.get::().floating_opacity) as u8); diff --git a/src/window/window_wrapper/keyboard_manager.rs b/src/window/keyboard_manager.rs similarity index 100% rename from src/window/window_wrapper/keyboard_manager.rs rename to src/window/keyboard_manager.rs diff --git a/src/window/mod.rs b/src/window/mod.rs index 3e522f0..08169ab 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -1,5 +1,351 @@ +mod keyboard_manager; +mod mouse_manager; +mod renderer; mod settings; -mod window_wrapper; -pub use settings::*; -pub use window_wrapper::create_window; +use std::{ + sync::mpsc::Receiver, + time::{Duration, Instant}, +}; + +use glutin::{ + self, + dpi::PhysicalSize, + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{self, Fullscreen, Icon}, + ContextBuilder, GlProfile, WindowedContext, +}; +use log::trace; + +#[cfg(target_os = "linux")] +use glutin::platform::unix::WindowBuilderExtUnix; + +use crate::{ + bridge::{ParallelCommand, UiCommand}, + channel_utils::*, + cmd_line::CmdLineSettings, + editor::DrawCommand, + editor::WindowCommand, + redraw_scheduler::REDRAW_SCHEDULER, + renderer::Renderer, + running_tracker::*, + settings::{maybe_save_window_size, SETTINGS}, + utils::Dimensions, +}; +use image::{load_from_memory, GenericImageView, Pixel}; +use keyboard_manager::KeyboardManager; +use mouse_manager::MouseManager; +use renderer::SkiaRenderer; + +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; + +pub struct GlutinWindowWrapper { + windowed_context: WindowedContext, + skia_renderer: SkiaRenderer, + renderer: Renderer, + keyboard_manager: KeyboardManager, + mouse_manager: MouseManager, + title: String, + fullscreen: bool, + saved_inner_size: PhysicalSize, + saved_grid_size: Option, + ui_command_sender: LoggingTx, + window_command_receiver: Receiver, +} + +impl GlutinWindowWrapper { + 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 synchronize_settings(&mut self) { + let fullscreen = { SETTINGS.get::().fullscreen }; + + if self.fullscreen != fullscreen { + self.toggle_fullscreen(); + } + } + + #[allow(clippy::needless_collect)] + pub fn handle_window_commands(&mut self) { + let window_commands: Vec = self.window_command_receiver.try_iter().collect(); + for window_command in window_commands.into_iter() { + match window_command { + WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title), + WindowCommand::SetMouseEnabled(mouse_enabled) => { + self.mouse_manager.enabled = mouse_enabled + } + } + } + } + + pub fn handle_title_changed(&mut self, new_title: String) { + self.title = new_title; + self.windowed_context.window().set_title(&self.title); + } + + pub fn handle_quit(&mut self) { + if SETTINGS.get::().remote_tcp.is_none() { + self.ui_command_sender + .send(ParallelCommand::Quit.into()) + .expect("Could not send quit command to bridge"); + } else { + RUNNING_TRACKER.quit("window closed"); + } + } + + pub fn handle_focus_lost(&mut self) { + self.ui_command_sender + .send(ParallelCommand::FocusLost.into()) + .ok(); + } + + pub fn handle_focus_gained(&mut self) { + self.ui_command_sender + .send(ParallelCommand::FocusGained.into()) + .ok(); + REDRAW_SCHEDULER.queue_next_frame(); + } + + pub fn handle_event(&mut self, event: Event<()>) { + self.keyboard_manager.handle_event(&event); + self.mouse_manager.handle_event( + &event, + &self.keyboard_manager, + &self.renderer, + &self.windowed_context, + ); + match event { + Event::LoopDestroyed => { + self.handle_quit(); + } + 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), + .. + } => { + self.ui_command_sender + .send( + ParallelCommand::FileDrop(path.into_os_string().into_string().unwrap()) + .into(), + ) + .ok(); + } + Event::WindowEvent { + event: WindowEvent::Focused(focus), + .. + } => { + if focus { + self.handle_focus_gained(); + } else { + self.handle_focus_lost(); + } + } + Event::RedrawRequested(..) | Event::WindowEvent { .. } => { + REDRAW_SCHEDULER.queue_next_frame() + } + _ => {} + } + } + + pub fn draw_frame(&mut self, dt: f32) { + let window = self.windowed_context.window(); + let mut font_changed = false; + + if REDRAW_SCHEDULER.should_draw() || SETTINGS.get::().no_idle { + font_changed = self.renderer.draw_frame(self.skia_renderer.canvas(), dt); + self.skia_renderer.gr_context.flush(None); + self.windowed_context.swap_buffers().unwrap(); + } + + // Wait until fonts are loaded, so we can set proper window size. + if !self.renderer.grid_renderer.is_ready { + return; + } + + let new_size = window.inner_size(); + let settings = SETTINGS.get::(); + // Resize at startup happens when window is maximized or when using tiling WM + // which already resized window. + let resized_at_startup = settings.maximized || is_already_resized(new_size); + + if self.saved_grid_size.is_none() && !resized_at_startup { + window.set_inner_size( + self.renderer + .grid_renderer + .convert_grid_to_physical(settings.geometry), + ); + self.saved_grid_size = Some(settings.geometry); + // Font change at startup is ignored, so grid size (and startup screen) could be preserved. + // But only when not resized yet. With maximized or resized window we should redraw grid. + font_changed = false; + } + + if self.saved_inner_size != new_size || font_changed { + self.saved_inner_size = new_size; + self.handle_new_grid_size(new_size); + self.skia_renderer.resize(&self.windowed_context); + } + } + + fn handle_new_grid_size(&mut self, new_size: PhysicalSize) { + let grid_size = self + .renderer + .grid_renderer + .convert_physical_to_grid(new_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); + self.ui_command_sender + .send( + ParallelCommand::Resize { + width: grid_size.width, + height: grid_size.height, + } + .into(), + ) + .ok(); + } + + fn handle_scale_factor_update(&mut self, scale_factor: f64) { + self.renderer + .grid_renderer + .handle_scale_factor_update(scale_factor); + } +} + +pub fn create_window( + batched_draw_command_receiver: Receiver>, + window_command_receiver: Receiver, + ui_command_sender: LoggingTx, +) { + let icon = { + let icon = load_from_memory(ICON).expect("Failed to parse icon data"); + let (width, height) = icon.dimensions(); + let mut rgba = Vec::with_capacity((width * height) as usize * 4); + for (_, _, pixel) in icon.pixels() { + rgba.extend_from_slice(&pixel.to_rgba().0); + } + Icon::from_rgba(rgba, width, height).expect("Failed to create icon object") + }; + + let event_loop = EventLoop::new(); + + let cmd_line_settings = SETTINGS.get::(); + let winit_window_builder = window::WindowBuilder::new() + .with_title("Neovide") + .with_window_icon(Some(icon)) + .with_maximized(cmd_line_settings.maximized) + .with_transparent(true) + .with_decorations(!cmd_line_settings.frameless); + + #[cfg(target_os = "linux")] + let winit_window_builder = winit_window_builder + .with_app_id(SETTINGS.get::().wayland_app_id) + .with_class( + "neovide".to_string(), + SETTINGS.get::().x11_wm_class, + ); + + let windowed_context = ContextBuilder::new() + .with_pixel_format(24, 8) + .with_stencil_buffer(8) + .with_gl_profile(GlProfile::Core) + .with_vsync(false) + .with_srgb(cmd_line_settings.srgb) + .build_windowed(winit_window_builder, &event_loop) + .unwrap(); + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + let window = windowed_context.window(); + + let scale_factor = windowed_context.window().scale_factor(); + let renderer = Renderer::new(batched_draw_command_receiver, scale_factor); + let saved_inner_size = window.inner_size(); + + let skia_renderer = SkiaRenderer::new(&windowed_context); + + log::info!( + "window created (scale_factor: {:.4}, font_dimensions: {:?})", + scale_factor, + renderer.grid_renderer.font_dimensions, + ); + + let mut window_wrapper = GlutinWindowWrapper { + windowed_context, + skia_renderer, + renderer, + keyboard_manager: KeyboardManager::new(ui_command_sender.clone()), + mouse_manager: MouseManager::new(ui_command_sender.clone()), + title: String::from("Neovide"), + fullscreen: false, + saved_inner_size, + saved_grid_size: None, + ui_command_sender, + window_command_receiver, + }; + + let mut previous_frame_start = Instant::now(); + + event_loop.run(move |e, _window_target, control_flow| { + if !RUNNING_TRACKER.is_running() { + maybe_save_window_size(window_wrapper.saved_grid_size); + std::process::exit(0); + } + + let frame_start = Instant::now(); + + window_wrapper.handle_window_commands(); + window_wrapper.synchronize_settings(); + window_wrapper.handle_event(e); + + let refresh_rate = { SETTINGS.get::().refresh_rate as f32 }; + 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); + previous_frame_start = frame_start; + } + + *control_flow = ControlFlow::WaitUntil(previous_frame_start + frame_duration) + }); +} + +fn is_already_resized(size: PhysicalSize) -> bool { + size != PhysicalSize::from((800, 600)) +} diff --git a/src/window/window_wrapper/mouse_manager.rs b/src/window/mouse_manager.rs similarity index 100% rename from src/window/window_wrapper/mouse_manager.rs rename to src/window/mouse_manager.rs diff --git a/src/window/window_wrapper/renderer.rs b/src/window/renderer.rs similarity index 100% rename from src/window/window_wrapper/renderer.rs rename to src/window/renderer.rs diff --git a/src/window/window_wrapper/mod.rs b/src/window/window_wrapper/mod.rs deleted file mode 100644 index fe8a416..0000000 --- a/src/window/window_wrapper/mod.rs +++ /dev/null @@ -1,349 +0,0 @@ -mod keyboard_manager; -mod mouse_manager; -mod renderer; - -use std::{ - sync::mpsc::Receiver, - time::{Duration, Instant}, -}; - -use glutin::{ - self, - dpi::PhysicalSize, - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{self, Fullscreen, Icon}, - ContextBuilder, GlProfile, WindowedContext, -}; -use log::trace; - -#[cfg(target_os = "linux")] -use glutin::platform::unix::WindowBuilderExtUnix; - -use super::settings::WindowSettings; -use crate::{ - bridge::{ParallelCommand, UiCommand}, - channel_utils::*, - cmd_line::CmdLineSettings, - editor::DrawCommand, - editor::WindowCommand, - redraw_scheduler::REDRAW_SCHEDULER, - renderer::Renderer, - running_tracker::*, - settings::{maybe_save_window_size, SETTINGS}, - utils::Dimensions, -}; -use image::{load_from_memory, GenericImageView, Pixel}; -use keyboard_manager::KeyboardManager; -use mouse_manager::MouseManager; -use renderer::SkiaRenderer; - -static ICON: &[u8] = include_bytes!("../../../assets/neovide.ico"); - -const MIN_WINDOW_WIDTH: u64 = 20; -const MIN_WINDOW_HEIGHT: u64 = 6; - -pub struct GlutinWindowWrapper { - windowed_context: WindowedContext, - skia_renderer: SkiaRenderer, - renderer: Renderer, - keyboard_manager: KeyboardManager, - mouse_manager: MouseManager, - title: String, - fullscreen: bool, - saved_inner_size: PhysicalSize, - saved_grid_size: Option, - ui_command_sender: LoggingTx, - window_command_receiver: Receiver, -} - -impl GlutinWindowWrapper { - 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 synchronize_settings(&mut self) { - let fullscreen = { SETTINGS.get::().fullscreen }; - - if self.fullscreen != fullscreen { - self.toggle_fullscreen(); - } - } - - #[allow(clippy::needless_collect)] - pub fn handle_window_commands(&mut self) { - let window_commands: Vec = self.window_command_receiver.try_iter().collect(); - for window_command in window_commands.into_iter() { - match window_command { - WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title), - WindowCommand::SetMouseEnabled(mouse_enabled) => { - self.mouse_manager.enabled = mouse_enabled - } - } - } - } - - pub fn handle_title_changed(&mut self, new_title: String) { - self.title = new_title; - self.windowed_context.window().set_title(&self.title); - } - - pub fn handle_quit(&mut self) { - if SETTINGS.get::().remote_tcp.is_none() { - self.ui_command_sender - .send(ParallelCommand::Quit.into()) - .expect("Could not send quit command to bridge"); - } else { - RUNNING_TRACKER.quit("window closed"); - } - } - - pub fn handle_focus_lost(&mut self) { - self.ui_command_sender - .send(ParallelCommand::FocusLost.into()) - .ok(); - } - - pub fn handle_focus_gained(&mut self) { - self.ui_command_sender - .send(ParallelCommand::FocusGained.into()) - .ok(); - REDRAW_SCHEDULER.queue_next_frame(); - } - - pub fn handle_event(&mut self, event: Event<()>) { - self.keyboard_manager.handle_event(&event); - self.mouse_manager.handle_event( - &event, - &self.keyboard_manager, - &self.renderer, - &self.windowed_context, - ); - match event { - Event::LoopDestroyed => { - self.handle_quit(); - } - 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), - .. - } => { - self.ui_command_sender - .send( - ParallelCommand::FileDrop(path.into_os_string().into_string().unwrap()) - .into(), - ) - .ok(); - } - Event::WindowEvent { - event: WindowEvent::Focused(focus), - .. - } => { - if focus { - self.handle_focus_gained(); - } else { - self.handle_focus_lost(); - } - } - Event::RedrawRequested(..) | Event::WindowEvent { .. } => { - REDRAW_SCHEDULER.queue_next_frame() - } - _ => {} - } - } - - pub fn draw_frame(&mut self, dt: f32) { - let window = self.windowed_context.window(); - let mut font_changed = false; - - if REDRAW_SCHEDULER.should_draw() || SETTINGS.get::().no_idle { - font_changed = self.renderer.draw_frame(self.skia_renderer.canvas(), dt); - self.skia_renderer.gr_context.flush(None); - self.windowed_context.swap_buffers().unwrap(); - } - - // Wait until fonts are loaded, so we can set proper window size. - if !self.renderer.grid_renderer.is_ready { - return; - } - - let new_size = window.inner_size(); - let settings = SETTINGS.get::(); - // Resize at startup happens when window is maximized or when using tiling WM - // which already resized window. - let resized_at_startup = settings.maximized || is_already_resized(new_size); - - if self.saved_grid_size.is_none() && !resized_at_startup { - window.set_inner_size( - self.renderer - .grid_renderer - .convert_grid_to_physical(settings.geometry), - ); - self.saved_grid_size = Some(settings.geometry); - // Font change at startup is ignored, so grid size (and startup screen) could be preserved. - // But only when not resized yet. With maximized or resized window we should redraw grid. - font_changed = false; - } - - if self.saved_inner_size != new_size || font_changed { - self.saved_inner_size = new_size; - self.handle_new_grid_size(new_size); - self.skia_renderer.resize(&self.windowed_context); - } - } - - fn handle_new_grid_size(&mut self, new_size: PhysicalSize) { - let grid_size = self - .renderer - .grid_renderer - .convert_physical_to_grid(new_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); - self.ui_command_sender - .send( - ParallelCommand::Resize { - width: grid_size.width, - height: grid_size.height, - } - .into(), - ) - .ok(); - } - - fn handle_scale_factor_update(&mut self, scale_factor: f64) { - self.renderer - .grid_renderer - .handle_scale_factor_update(scale_factor); - } -} - -pub fn create_window( - batched_draw_command_receiver: Receiver>, - window_command_receiver: Receiver, - ui_command_sender: LoggingTx, -) { - let icon = { - let icon = load_from_memory(ICON).expect("Failed to parse icon data"); - let (width, height) = icon.dimensions(); - let mut rgba = Vec::with_capacity((width * height) as usize * 4); - for (_, _, pixel) in icon.pixels() { - rgba.extend_from_slice(&pixel.to_rgba().0); - } - Icon::from_rgba(rgba, width, height).expect("Failed to create icon object") - }; - - let event_loop = EventLoop::new(); - - let cmd_line_settings = SETTINGS.get::(); - let winit_window_builder = window::WindowBuilder::new() - .with_title("Neovide") - .with_window_icon(Some(icon)) - .with_maximized(cmd_line_settings.maximized) - .with_transparent(true) - .with_decorations(!cmd_line_settings.frameless); - - #[cfg(target_os = "linux")] - let winit_window_builder = winit_window_builder - .with_app_id(SETTINGS.get::().wayland_app_id) - .with_class( - "neovide".to_string(), - SETTINGS.get::().x11_wm_class, - ); - - let windowed_context = ContextBuilder::new() - .with_pixel_format(24, 8) - .with_stencil_buffer(8) - .with_gl_profile(GlProfile::Core) - .with_vsync(false) - .with_srgb(cmd_line_settings.srgb) - .build_windowed(winit_window_builder, &event_loop) - .unwrap(); - let windowed_context = unsafe { windowed_context.make_current().unwrap() }; - - let window = windowed_context.window(); - - let scale_factor = windowed_context.window().scale_factor(); - let renderer = Renderer::new(batched_draw_command_receiver, scale_factor); - let saved_inner_size = window.inner_size(); - - let skia_renderer = SkiaRenderer::new(&windowed_context); - - log::info!( - "window created (scale_factor: {:.4}, font_dimensions: {:?})", - scale_factor, - renderer.grid_renderer.font_dimensions, - ); - - let mut window_wrapper = GlutinWindowWrapper { - windowed_context, - skia_renderer, - renderer, - keyboard_manager: KeyboardManager::new(ui_command_sender.clone()), - mouse_manager: MouseManager::new(ui_command_sender.clone()), - title: String::from("Neovide"), - fullscreen: false, - saved_inner_size, - saved_grid_size: None, - ui_command_sender, - window_command_receiver, - }; - - let mut previous_frame_start = Instant::now(); - - event_loop.run(move |e, _window_target, control_flow| { - if !RUNNING_TRACKER.is_running() { - maybe_save_window_size(window_wrapper.saved_grid_size); - std::process::exit(0); - } - - let frame_start = Instant::now(); - - window_wrapper.handle_window_commands(); - window_wrapper.synchronize_settings(); - window_wrapper.handle_event(e); - - let refresh_rate = { SETTINGS.get::().refresh_rate as f32 }; - 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); - previous_frame_start = frame_start; - } - - *control_flow = ControlFlow::WaitUntil(previous_frame_start + frame_duration) - }); -} - -fn is_already_resized(size: PhysicalSize) -> bool { - size != PhysicalSize::from((800, 600)) -}