mirror of
https://github.com/neovide/neovide.git
synced 2026-09-10 07:16:25 -04:00
Extract window_wrapper and the update loop to their own modules (#1939)
This commit is contained in:
parent
3dee458ee4
commit
39af18d647
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -60,11 +60,10 @@ fn gen_config(mut config_iterator: Box<dyn Iterator<Item = Config> + '_>) -> Con
|
|||
config_iterator.next().unwrap()
|
||||
}
|
||||
|
||||
pub fn build_context<TE>(
|
||||
cmd_line_settings: &CmdLineSettings,
|
||||
pub fn build_window<TE>(
|
||||
winit_window_builder: WindowBuilder,
|
||||
event_loop: &EventLoop<TE>,
|
||||
) -> Context {
|
||||
) -> (Window, Config) {
|
||||
let template_builder = ConfigTemplateBuilder::new()
|
||||
.with_stencil_size(8)
|
||||
.with_transparency(true);
|
||||
|
|
@ -72,8 +71,14 @@ pub fn build_context<TE>(
|
|||
.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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<u32>,
|
||||
saved_grid_size: Option<Dimensions>,
|
||||
size_at_startup: PhysicalSize<u32>,
|
||||
maximized_at_startup: bool,
|
||||
window_command_receiver: UnboundedReceiver<WindowCommand>,
|
||||
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::<WindowSettings>().fullscreen };
|
||||
|
||||
if self.fullscreen != fullscreen {
|
||||
self.toggle_fullscreen();
|
||||
}
|
||||
|
||||
let ime_enabled = { SETTINGS.get::<KeyboardSettings>().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::<CmdLineSettings>().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::<WindowSettings>();
|
||||
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::<WindowSettings>();
|
||||
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::<WindowSettings>().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::<CmdLineSettings>();
|
||||
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<u32>) {
|
||||
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::<WindowCommand>();
|
||||
|
||||
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::<KeyboardSettings>().ime };
|
||||
|
||||
match SETTINGS.get::<WindowSettings>().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::<WindowSettings>().refresh_rate as f32
|
||||
}
|
||||
FocusedState::Unfocused => SETTINGS.get::<WindowSettings>().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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
117
src/window/update_loop.rs
Normal file
117
src/window/update_loop.rs
Normal file
|
|
@ -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::<WindowSettings>().refresh_rate as f32
|
||||
}
|
||||
FocusedState::Unfocused => SETTINGS.get::<WindowSettings>().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::<WindowSettings>().refresh_rate as f32
|
||||
}
|
||||
FocusedState::Unfocused => SETTINGS.get::<WindowSettings>().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)
|
||||
}
|
||||
}
|
||||
427
src/window/window_wrapper.rs
Normal file
427
src/window/window_wrapper.rs
Normal file
|
|
@ -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<u32>,
|
||||
saved_grid_size: Option<Dimensions>,
|
||||
size_at_startup: PhysicalSize<u32>,
|
||||
maximized_at_startup: bool,
|
||||
window_command_receiver: UnboundedReceiver<WindowCommand>,
|
||||
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<PhysicalPosition<i32>>,
|
||||
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::<WindowCommand>();
|
||||
|
||||
log::info!(
|
||||
"window created (scale_factor: {:.4}, font_dimensions: {:?})",
|
||||
scale_factor,
|
||||
renderer.grid_renderer.font_dimensions,
|
||||
);
|
||||
|
||||
let ime_enabled = { SETTINGS.get::<KeyboardSettings>().ime };
|
||||
|
||||
match SETTINGS.get::<WindowSettings>().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::<WindowSettings>().fullscreen };
|
||||
|
||||
if self.fullscreen != fullscreen {
|
||||
self.toggle_fullscreen();
|
||||
}
|
||||
|
||||
let ime_enabled = { SETTINGS.get::<KeyboardSettings>().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::<CmdLineSettings>().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::<WindowSettings>();
|
||||
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::<WindowSettings>();
|
||||
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::<WindowSettings>().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::<CmdLineSettings>();
|
||||
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<u32>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue