use std::{
cell::RefCell,
fmt,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use log::trace;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use rustc_hash::FxHashMap;
use winit::{
dpi,
event::{Ime, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy},
window::{Cursor, Fullscreen, Theme, Window, WindowId},
};
#[cfg(target_os = "windows")]
use super::settings::CornerPreference;
use super::{
EventPayload, EventTarget, KeyboardManager, MessageSelectionEvent, MouseManager, OverlayEvent,
RouteId, UserEvent, WindowCommand, WindowSettings, WindowSettingsChanged, WindowSize,
};
#[cfg(target_os = "macos")]
use {
crate::units::{GridPos, Pixel},
crate::window::MacShortcutCommand,
crate::window::macos::tab_navigation::{TabNavigationAction, TabNavigationHotkeys},
crate::window::macos::{
MacosWindowFeature, TouchpadStage, hide_application, is_focus_suppressed,
is_tab_overview_active, native_tab_bar_enabled, trigger_tab_overview,
},
crate::{error_msg, window::settings},
glamour::Point2,
std::collections::VecDeque,
winit::platform::macos::{self, WindowExtMacOS},
};
use crate::{
CmdLineSettings,
bridge::{
NeovimHandler, NeovimRuntime, OpenArgs, OpenMode, ParallelCommand, RestartDetails,
SerialCommand, send_ui, set_active_route_handler, unregister_route_handler,
},
clipboard::ClipboardHandle,
cmd_line::{GeometryArgs, MouseCursorIcon},
profiling::{tracy_frame, tracy_gpu_collect, tracy_gpu_zone, tracy_plot, tracy_zone},
renderer::{
DrawCommand, MessageSelection, Renderer, RendererSettingsChanged, SkiaRenderer, VSync,
create_skia_renderer,
},
running_tracker::RunningTracker,
settings::{
Config, DEFAULT_GRID_SIZE, MIN_GRID_SIZE, RendererHotReloadConfigs, Settings,
SettingsChanged, WindowHotReloadConfigs, clamped_grid_size, font::FontSettings,
load_last_window_settings,
},
units::{GridRect, GridScale, GridSize, PixelPos, PixelRect, PixelSize},
window::{
PhysicalSize, ShouldRender, ThemeSettings, create_window, determine_grid_size,
determine_window_size,
},
};
#[cfg(windows)]
use {
crate::windows_utils::{register_right_click, unregister_right_click},
winit::platform::windows::{BackdropType, Color, WindowExtWindows},
};
const GRID_TOLERANCE: f32 = 1e-3;
fn round_or_op<Op: FnOnce(f32) -> f32>(v: f32, op: Op) -> f32 {
let rounded = v.round();
if v.abs_diff_eq(&rounded, GRID_TOLERANCE) { rounded } else { op(v) }
}
use approx::AbsDiffEq;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct WindowPadding {
pub top: u32,
pub left: u32,
pub right: u32,
pub bottom: u32,
}
#[derive(PartialEq, PartialOrd)]
enum UIState {
Initing, WaitingForWindowCreate,
FirstFrame,
Showing, }
enum GeometryTarget {
Maximized,
Size(PhysicalSize<u32>),
Grid(GridSize<u32>),
None,
}
pub struct RouteWindow {
pub skia_renderer: Rc<RefCell<Box<dyn SkiaRenderer>>>,
pub winit_window: Rc<Window>,
pub neovim_handler: NeovimHandler,
pub mouse_manager: Rc<RefCell<Box<MouseManager>>>,
pub renderer: Rc<RefCell<Box<Renderer>>>,
#[cfg(target_os = "macos")]
pub macos_feature: Option<Rc<RefCell<Box<MacosWindowFeature>>>>,
pub title: String,
pub last_applied_window_size: dpi::PhysicalSize<u32>,
pub last_synced_grid_size: Option<GridSize<u32>>,
}
impl fmt::Debug for RouteWindow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("RouteWindow")
.field("skia_renderer", &"...")
.field("winit_window", &self.winit_window)
.field("neovim_handler", &self.neovim_handler)
.finish()
}
}
pub struct Route {
pub route_id: RouteId,
pub window: RouteWindow,
cwd: Option<PathBuf>,
pub pending_initial_window_size: Option<WindowSize>,
state: RouteState,
}
impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Route").field("window", &self.window).field("state", &"...").finish()
}
}
struct RouteState {
font_changed_last_frame: bool,
saved_inner_size: dpi::PhysicalSize<u32>,
saved_grid_size: Option<GridSize<u32>>,
requested_columns: Option<u32>,
requested_lines: Option<u32>,
window_padding: WindowPadding,
is_minimized: bool,
ime_enabled: bool,
ime_area: (dpi::PhysicalPosition<u32>, dpi::PhysicalSize<u32>),
inferred_theme: Option<Theme>,
vsync: Option<VSync>,
}
impl RouteState {
fn new() -> Self {
Self {
font_changed_last_frame: false,
saved_inner_size: Default::default(),
saved_grid_size: None,
requested_columns: None,
requested_lines: None,
window_padding: WindowPadding { left: 0, right: 0, top: 0, bottom: 0 },
is_minimized: false,
ime_enabled: false,
ime_area: Default::default(),
inferred_theme: None,
vsync: None,
}
}
}
struct RouteCore {
route_id: RouteId,
renderer: Rc<RefCell<Box<Renderer>>>,
neovim_handler: NeovimHandler,
cwd: Option<PathBuf>,
title: String,
mouse_enabled: bool,
pending_initial_window_size: Option<WindowSize>,
last_synced_grid_size: Option<GridSize<u32>>,
inferred_theme: Option<Theme>,
should_show_observed: bool,
font_changed_last_frame: bool,
}
#[derive(Clone)]
struct RestartRequest {
details: RestartDetails,
grid_size: GridSize<u32>,
}
pub struct WinitWindowWrapper {
pub routes: FxHashMap<WindowId, Route>,
route_cores: FxHashMap<RouteId, RouteCore>,
pending_window_creation_route: Option<RouteId>,
pub runtime: Option<NeovimRuntime>,
pub runtime_tracker: RunningTracker,
pending_restart: FxHashMap<RouteId, RestartRequest>,
keyboard_manager: KeyboardManager,
ui_state: UIState,
settings: Arc<Settings>,
clipboard: ClipboardHandle,
startup_error: Option<String>,
#[cfg(target_os = "macos")]
window_mru: VecDeque<WindowId>,
#[cfg(target_os = "macos")]
focus_return_target: Option<WindowId>,
#[cfg(target_os = "macos")]
ignore_next_focus_gain: bool,
#[cfg(target_os = "macos")]
tab_navigation_hotkeys: TabNavigationHotkeys,
}
impl WinitWindowWrapper {
pub fn new(
_initial_font_settings: Option<FontSettings>,
settings: Arc<Settings>,
runtime_tracker: RunningTracker,
clipboard_handle: ClipboardHandle,
) -> Self {
let (runtime, startup_error) = match NeovimRuntime::new(clipboard_handle.clone()) {
Ok(rt) => (Some(rt), None),
Err(e) => {
let msg = format!("Failed to create neovim runtime: {e:?}");
log::error!("{msg}");
(None, Some(msg))
}
};
Self {
routes: Default::default(),
route_cores: FxHashMap::default(),
pending_window_creation_route: None,
runtime,
runtime_tracker,
pending_restart: FxHashMap::default(),
keyboard_manager: KeyboardManager::new(settings.clone()),
ui_state: UIState::Initing,
settings: settings.clone(),
clipboard: clipboard_handle,
startup_error,
#[cfg(target_os = "macos")]
window_mru: VecDeque::new(),
#[cfg(target_os = "macos")]
focus_return_target: None,
#[cfg(target_os = "macos")]
ignore_next_focus_gain: false,
#[cfg(target_os = "macos")]
tab_navigation_hotkeys: TabNavigationHotkeys::new(settings.as_ref()),
}
}
fn report_startup_error(&self, proxy: &EventLoopProxy<EventPayload>, message: String) {
self.runtime_tracker.quit_with_code(1, &message);
let _ = proxy.send_event(EventPayload::all(UserEvent::NeovimLaunchError { message }));
}
pub fn request_window_creation(&mut self, proxy: &EventLoopProxy<EventPayload>) {
if !self.routes.is_empty() || !self.route_cores.is_empty() {
return;
}
if let Some(error) = self.startup_error.take() {
self.report_startup_error(proxy, error);
return;
}
let persisted_window_settings = load_last_window_settings().ok();
let desired_window_size =
determine_window_size(persisted_window_settings.as_ref(), &self.settings.clone());
let desired_grid_size =
determine_grid_size(&desired_window_size, persisted_window_settings);
let pending_initial_window_size = match desired_window_size.clone() {
WindowSize::Grid(_) | WindowSize::NeovimGrid => Some(desired_window_size),
WindowSize::Maximized | WindowSize::Size(_) => None,
};
let config = Config::init();
let renderer = Rc::new(RefCell::new(Box::new(Renderer::new(
1.0,
config.clone(),
self.settings.clone(),
))));
let route_id = RouteId::next();
let runtime = self.runtime.as_mut().expect("Neovim runtime has not been initialized");
let neovim_handler = match runtime.launch(
route_id,
proxy.clone(),
desired_grid_size,
self.runtime_tracker.clone(),
self.settings.clone(),
&config,
None,
OpenMode::Startup,
) {
Ok(handler) => handler,
Err(err) => {
let msg = format!("Failed to launch neovim runtime: {err:?}");
log::error!("{msg}");
self.report_startup_error(proxy, msg);
return;
}
};
self.route_cores.insert(
route_id,
RouteCore {
route_id,
renderer,
neovim_handler,
cwd: None,
title: String::from("Neovide"),
mouse_enabled: true,
pending_initial_window_size,
last_synced_grid_size: None,
inferred_theme: None,
should_show_observed: false,
font_changed_last_frame: false,
},
);
}
pub fn has_pending_window_creation(&self) -> bool {
self.pending_window_creation_route.is_some()
}
pub fn is_empty(&self) -> bool {
self.routes.is_empty() && self.route_cores.is_empty()
}
pub fn exit(&mut self) {
for route in self.routes.values_mut() {
route.state.vsync = None;
}
}
pub fn set_fullscreen(&mut self, window_id: WindowId, fullscreen: bool) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let window = route.window.winit_window.clone();
if fullscreen {
let handle = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(handle)));
} else {
window.set_fullscreen(None);
}
}
#[cfg(target_os = "windows")]
fn set_corner_preference(&self, window_id: WindowId, option: CornerPreference) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let skia_renderer = route.window.skia_renderer.borrow();
skia_renderer.window().set_corner_preference(option.into());
}
#[cfg(target_os = "macos")]
pub fn set_macos_option_as_meta(
&mut self,
window_id: WindowId,
option: settings::OptionAsMeta,
) {
let winit_option = match option {
settings::OptionAsMeta::OnlyLeft => macos::OptionAsAlt::OnlyLeft,
settings::OptionAsMeta::OnlyRight => macos::OptionAsAlt::OnlyRight,
settings::OptionAsMeta::Both => macos::OptionAsAlt::Both,
settings::OptionAsMeta::None => macos::OptionAsAlt::None,
};
let Some(route) = self.routes.get(&window_id) else {
return;
};
let window = route.window.winit_window.clone();
if winit_option != window.option_as_alt() {
window.set_option_as_alt(winit_option);
}
}
#[cfg(target_os = "macos")]
pub fn set_simple_fullscreen(&mut self, window_id: WindowId, fullscreen: bool) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let window = route.window.winit_window.clone();
let Some(feature) = &route.window.macos_feature else {
window.set_simple_fullscreen(fullscreen);
return;
};
if fullscreen {
feature.borrow_mut().set_simple_fullscreen_mode(true);
window.set_simple_fullscreen(true);
} else {
window.set_simple_fullscreen(false);
feature.borrow_mut().set_simple_fullscreen_mode(false);
}
}
pub fn minimize_window(&mut self) {
let Some(route) = self.focused_route() else {
return;
};
let window = route.window.winit_window.clone();
window.set_minimized(true);
}
pub fn set_ime(&mut self, window_id: WindowId, ime_enabled: bool) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let window = route.window.winit_window.clone();
window.set_ime_allowed(ime_enabled);
}
pub fn handle_window_command(&mut self, target: EventTarget, command: WindowCommand) {
tracy_zone!("handle_window_commands", 0);
if let EventTarget::Route(route_id) = target {
if self.window_id_for_route(route_id).is_none() {
self.handle_route_core_window_command(route_id, command);
return;
}
}
let Some(target_window_id) = self.resolve_target_window_id(target) else {
return;
};
match command {
WindowCommand::TitleChanged(new_title) => {
self.handle_title_changed(target_window_id, new_title)
}
WindowCommand::SetMouseEnabled(mouse_enabled) => {
if let Some(route) = self.routes.get(&target_window_id) {
let mut mouse_manager = route.window.mouse_manager.borrow_mut();
mouse_manager.enabled = mouse_enabled;
}
}
WindowCommand::ListAvailableFonts => self.send_font_names(target_window_id),
WindowCommand::FocusWindow => {
if let Some(route) = &self.routes.get(&target_window_id) {
let window = route.window.winit_window.clone();
window.focus_window();
}
#[cfg(target_os = "macos")]
if let Some(feature) = self.macos_feature_for_window(target_window_id) {
feature.borrow().activate_application();
}
}
#[cfg(target_os = "macos")]
WindowCommand::TouchpadPressure { col, row, entity, guifont, kind } => {
let Some(macos_feature) = self.macos_feature_for_window(target_window_id) else {
log::warn!("Touchpad pressure received before macOS feature initialization");
return;
};
let titlebar_height = macos_feature.borrow().system_titlebar_height as f32;
let window_padding = self.calculate_window_padding(target_window_id);
let pixel_position = self.grid_to_pixel_position(target_window_id, col, row);
let Some(grid_scale_height) = self.routes.get(&target_window_id).map(|route| {
let renderer = route.window.renderer.borrow();
renderer.grid_renderer.grid_scale.height()
}) else {
return;
};
let point =
self.apply_padding_to_position(pixel_position, window_padding, titlebar_height);
macos_feature.borrow_mut().handle_force_click_target(
&entity,
kind,
point,
guifont,
grid_scale_height,
);
}
#[cfg(target_os = "macos")]
WindowCommand::HighlightMatchingPair { grid, row, column, text } => {
use crate::renderer::rendered_window::BASE_GRID_ID;
use crate::renderer::rendered_window::NO_MULTIGRID_GRID_ID;
let target_grid = if grid == NO_MULTIGRID_GRID_ID { BASE_GRID_ID } else { grid };
let Some(route) = self.routes.get(&target_window_id) else {
return;
};
let rect = {
let renderer = route.window.renderer.borrow();
let grid_scale = renderer.grid_renderer.grid_scale;
let cell_size = PixelSize::new(grid_scale.width(), grid_scale.height());
let grid_pos = GridPos::new(column as f32, row as f32);
renderer.rendered_windows.get(&target_grid).map(|window| {
let mut adjusted_grid = grid_pos + window.grid_current_position.to_vector();
adjusted_grid.y -= window.scroll_animation.position;
let origin = adjusted_grid * grid_scale;
PixelRect::from_origin_and_size(origin, cell_size)
})
};
if let (Some(rect), Some(macos_feature)) =
(rect, self.macos_feature_for_window(target_window_id))
{
macos_feature.borrow_mut().show_find_indicator_for_rect(rect, text.as_deref());
}
}
WindowCommand::Minimize => {
self.minimize_window();
if let Some(route) = self.routes.get_mut(&target_window_id) {
route.state.is_minimized = true;
}
}
WindowCommand::ThemeChanged(new_theme) => {
if let Some(route) = self.routes.get_mut(&target_window_id) {
if route.state.inferred_theme != new_theme {
route.state.inferred_theme = new_theme;
let WindowSettings { theme, .. } = self.settings.get::<WindowSettings>();
if matches!(theme, ThemeSettings::BgColor) {
self.apply_theme_for_window(target_window_id);
}
}
}
}
#[cfg(windows)]
WindowCommand::RegisterRightClick => register_right_click(),
#[cfg(windows)]
WindowCommand::UnregisterRightClick => unregister_right_click(),
}
}
fn handle_route_core_window_command(&mut self, route_id: RouteId, command: WindowCommand) {
let Some(route_core) = self.route_cores.get_mut(&route_id) else {
return;
};
match command {
WindowCommand::TitleChanged(new_title) => {
route_core.title = new_title;
}
WindowCommand::SetMouseEnabled(mouse_enabled) => {
route_core.mouse_enabled = mouse_enabled;
}
WindowCommand::ThemeChanged(new_theme) => {
route_core.inferred_theme = new_theme;
}
WindowCommand::ListAvailableFonts => {
let renderer = route_core.renderer.borrow();
let font_names = renderer.font_names();
send_ui(
ParallelCommand::DisplayAvailableFonts(font_names),
&route_core.neovim_handler,
);
}
_ => {}
}
}
pub fn handle_window_settings_changed(
&mut self,
target: EventTarget,
changed_setting: WindowSettingsChanged,
) {
tracy_zone!("handle_window_settings_changed");
let window_ids = self.window_ids_for_target(target);
match changed_setting {
WindowSettingsChanged::ObservedColumns(columns) => {
log::info!("columns changed");
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get_mut(window_id) {
route.state.requested_columns =
columns.and_then(|v| match u32::try_from(v) {
Ok(value) => Some(value),
Err(_) => {
log::warn!("Invalid columns value {v}, ignoring");
None
}
});
}
}
}
WindowSettingsChanged::ObservedLines(lines) => {
log::info!("lines changed");
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get_mut(window_id) {
route.state.requested_lines = lines.and_then(|v| match u32::try_from(v) {
Ok(value) => Some(value),
Err(_) => {
log::warn!("Invalid lines value {v}, ignoring");
None
}
});
}
}
}
WindowSettingsChanged::Fullscreen(fullscreen) => {
for window_id in window_ids.iter() {
self.set_fullscreen(*window_id, fullscreen);
}
}
WindowSettingsChanged::InputIme(ime_enabled) => {
for window_id in window_ids.iter() {
self.set_ime(*window_id, ime_enabled);
}
}
WindowSettingsChanged::ScaleFactor(user_scale_factor) => {
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get_mut(window_id) {
let mut renderer = route.window.renderer.borrow_mut();
renderer.handle_user_scale_factor_change(user_scale_factor.into());
route.state.font_changed_last_frame = true;
}
}
}
WindowSettingsChanged::WindowBlurred(blur) => {
let WindowSettings { opacity, .. } = self.settings.get::<WindowSettings>();
let transparent = opacity < 1.0;
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get(window_id) {
let window = route.window.winit_window.clone();
window.set_blur(blur && transparent);
}
}
}
WindowSettingsChanged::MessageAreaDragSelection(enabled) => {
if !enabled {
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get(window_id) {
route.window.mouse_manager.borrow_mut().clear_message_selection();
route.window.renderer.borrow_mut().set_message_selection(None);
}
}
}
}
WindowSettingsChanged::Opacity(..) | WindowSettingsChanged::NormalOpacity(..) => {
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get(window_id) {
let mut renderer = route.window.renderer.borrow_mut();
renderer.prepare_lines(true);
}
}
}
WindowSettingsChanged::Theme(..) => {
for window_id in window_ids.iter() {
self.apply_theme_for_window(*window_id);
}
}
#[cfg(target_os = "windows")]
WindowSettingsChanged::TitleBackgroundColor(color) => {
for window_id in window_ids.iter() {
self.handle_title_background_color(*window_id, &color);
}
}
#[cfg(target_os = "windows")]
WindowSettingsChanged::TitleTextColor(color) => {
for window_id in window_ids.iter() {
self.handle_title_text_color(*window_id, &color);
}
}
#[cfg(target_os = "windows")]
WindowSettingsChanged::CornerPreference(option) => {
for window_id in window_ids.iter() {
self.set_corner_preference(*window_id, option);
}
}
#[cfg(target_os = "macos")]
WindowSettingsChanged::InputMacosOptionKeyIsMeta(option) => {
for window_id in window_ids.iter() {
self.set_macos_option_as_meta(*window_id, option);
}
}
#[cfg(target_os = "macos")]
WindowSettingsChanged::InputMacosAltIsMeta(enabled) => {
if enabled {
error_msg!(concat!(
"neovide_input_macos_alt_is_meta has now been removed. ",
"Use neovide_input_macos_option_key_is_meta instead. ",
"Please check https://neovide.dev/configuration.html#macos-option-key-is-meta for more information.",
));
}
}
#[cfg(target_os = "macos")]
WindowSettingsChanged::MacosSimpleFullscreen(fullscreen) => {
for window_id in window_ids.iter() {
self.set_simple_fullscreen(*window_id, fullscreen);
}
}
_ => {}
}
#[cfg(target_os = "macos")]
{
for window_id in window_ids.iter() {
if let Some(macos_feature) = self.macos_feature_for_window(*window_id) {
macos_feature.borrow_mut().handle_settings_changed(changed_setting.clone());
}
}
}
}
fn handle_render_settings_changed(
&mut self,
target: EventTarget,
changed_setting: RendererSettingsChanged,
) {
let window_ids = self.window_ids_for_target(target);
match changed_setting {
RendererSettingsChanged::TextGamma(..)
| RendererSettingsChanged::TextContrast(..)
| RendererSettingsChanged::PixelGeometry(..) => {
for window_id in window_ids.iter() {
if let Some(route) = self.routes.get_mut(window_id) {
let mut skia_renderer = route.window.skia_renderer.borrow_mut();
skia_renderer.resize();
route.state.font_changed_last_frame = true;
}
}
}
_ => {}
}
}
pub fn handle_title_changed(&mut self, window_id: WindowId, new_title: String) {
if let Some(route) = self.routes.get_mut(&window_id) {
let window = route.window.winit_window.clone();
route.window.title = new_title.clone();
window.set_title(&new_title);
}
}
fn get_theme_for(&self, inferred_theme: Option<Theme>) -> Option<Theme> {
let WindowSettings { theme, .. } = self.settings.get::<WindowSettings>();
match theme {
ThemeSettings::Auto => None,
ThemeSettings::Light => Some(Theme::Light),
ThemeSettings::Dark => Some(Theme::Dark),
ThemeSettings::BgColor => inferred_theme,
}
}
fn apply_theme_for_window(&self, window_id: WindowId) {
if let Some(route) = self.routes.get(&window_id) {
let window = route.window.winit_window.clone();
let theme = self.get_theme_for(route.state.inferred_theme);
window.set_theme(theme);
}
}
pub fn send_font_names(&self, window_id: WindowId) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let renderer = route.window.renderer.borrow();
let neovim_handler = &route.window.neovim_handler;
let font_names = renderer.font_names();
send_ui(ParallelCommand::DisplayAvailableFonts(font_names), neovim_handler);
}
pub fn handle_quit(&mut self, window_id: WindowId) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let neovim_handler = &route.window.neovim_handler;
send_ui(ParallelCommand::Quit, neovim_handler);
}
pub fn handle_focus_lost(&mut self, window_id: WindowId) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let neovim_handler = &route.window.neovim_handler;
send_ui(ParallelCommand::FocusLost, neovim_handler);
}
pub fn handle_focus_gained(&mut self, window_id: WindowId) {
{
let Some(route) = self.routes.get(&window_id) else {
return;
};
set_active_route_handler(route.route_id);
let neovim_handler = &route.window.neovim_handler;
send_ui(ParallelCommand::FocusGained, neovim_handler);
if route.state.is_minimized {
send_ui(SerialCommand::Keyboard("<NOP>".into()), neovim_handler);
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.is_minimized = false;
}
}
}
#[cfg(target_os = "macos")]
self.handle_focus_gain_for_shortcuts(window_id);
}
fn preprocess_window_input(
&mut self,
window_id: WindowId,
event: &WindowEvent,
) -> Option<OverlayEvent> {
let route = self.routes.get_mut(&window_id)?;
let neovim_handler = &route.window.neovim_handler;
#[cfg(target_os = "macos")]
let mut consumed_key_event = false;
#[cfg(target_os = "macos")]
{
if native_tab_bar_enabled() {
if let WindowEvent::KeyboardInput { event: key_event, .. } = event {
let modifiers = self.keyboard_manager.current_modifiers();
if let Some(action) =
self.tab_navigation_hotkeys.action_for(key_event, &modifiers)
{
if let Some(feature) = &route.window.macos_feature {
let feature_ref = feature.borrow();
if feature_ref.can_navigate_tabs() {
match action {
TabNavigationAction::Next => feature_ref.select_next_tab(),
TabNavigationAction::Previous => {
feature_ref.select_previous_tab()
}
}
consumed_key_event = true;
}
}
}
}
}
}
let mouse_result = {
let mut mouse_manager = route.window.mouse_manager.borrow_mut();
let window = route.window.winit_window.clone();
let renderer = route.window.renderer.borrow();
mouse_manager.handle_event(
event,
&self.keyboard_manager,
&renderer,
&window,
neovim_handler,
)
};
#[cfg(target_os = "macos")]
if !consumed_key_event {
self.keyboard_manager.handle_event(event, neovim_handler);
}
#[cfg(not(target_os = "macos"))]
self.keyboard_manager.handle_event(event, neovim_handler);
{
let mut renderer = route.window.renderer.borrow_mut();
renderer.handle_event(event);
}
Some(mouse_result.overlay_event)
}
pub fn handle_window_event(&mut self, window_id: WindowId, event: WindowEvent) -> bool {
let Some(overlay_event) = self.preprocess_window_input(window_id, &event) else {
return false;
};
let message_selection_needs_render = match overlay_event {
OverlayEvent::Unchanged => false,
OverlayEvent::MessageSelection(action) => {
self.apply_message_selection_event(window_id, action)
}
};
let mut should_render = true;
let mut pending_focus_event: Option<bool> = None;
#[cfg(target_os = "macos")]
let mut resized = false;
{
let Some(route) = self.routes.get_mut(&window_id) else {
return false;
};
let neovim_handler = &route.window.neovim_handler;
match event {
WindowEvent::CloseRequested => {
tracy_zone!("CloseRequested");
self.handle_quit(window_id);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
tracy_zone!("ScaleFactorChanged");
self.handle_scale_factor_update(window_id, scale_factor);
}
WindowEvent::Resized { .. } => {
let mut skia_renderer = route.window.skia_renderer.borrow_mut();
skia_renderer.resize();
#[cfg(target_os = "macos")]
{
resized = true;
if let Some(macos_feature) = &mut route.window.macos_feature {
macos_feature.borrow_mut().handle_size_changed();
}
}
}
WindowEvent::DroppedFile(path) => {
tracy_zone!("DroppedFile");
let file_path = match path.into_os_string().into_string() {
Ok(path) => path,
Err(path) => {
log::warn!(
"Dropped file path is not valid UTF-8; skipping to avoid path corruption: {:?}",
path
);
return false;
}
};
send_ui(
ParallelCommand::FileDrop { path: file_path, tabs: None },
neovim_handler,
);
}
WindowEvent::Focused(focus) => {
tracy_zone!("Focused");
pending_focus_event = Some(focus);
}
WindowEvent::Moved(_) => {
tracy_zone!("Moved");
let window = route.window.winit_window.clone();
if let Some(vsync) = route.state.vsync.as_mut() {
vsync.update(&window);
}
}
#[cfg(target_os = "macos")]
WindowEvent::TouchpadPressure { stage, .. } => {
tracy_zone!("TouchpadPressure");
if let Some(macos_feature) = &route.window.macos_feature {
match TouchpadStage::from_stage(stage) {
TouchpadStage::Soft | TouchpadStage::Click => {
macos_feature.borrow_mut().set_definition_is_active(false)
}
TouchpadStage::ForceClick => {
macos_feature.borrow_mut().handle_touchpad_force_click()
}
}
}
}
WindowEvent::Ime(Ime::Enabled) => {
log::info!("Ime enabled");
route.state.ime_enabled = true;
self.update_ime_position(window_id, true);
}
WindowEvent::Ime(Ime::Disabled) => {
log::info!("Ime disabled");
route.state.ime_enabled = false;
}
_ => {
tracy_zone!("Unknown WindowEvent");
should_render = false;
}
}
}
#[cfg(target_os = "macos")]
if resized {
self.sync_native_tabs_resize(window_id);
}
should_render |= message_selection_needs_render;
if let Some(focus) = pending_focus_event {
#[cfg(target_os = "macos")]
{
if is_focus_suppressed() {
log::trace!("Suppressing focus event during tab detach (focus = {})", focus);
return self.ui_state >= UIState::FirstFrame && should_render;
}
if focus {
if let Some(route) = self.routes.get(&window_id) {
let ns_window =
crate::window::macos::get_ns_window(route.window.winit_window.as_ref());
let host_ptr = crate::window::macos::get_last_host_window();
let window_ptr =
crate::window::macos::window_identifier(ns_window.as_ref());
if host_ptr != 0 && window_ptr != host_ptr {
log::trace!(
"Focus gained for non-host window; refocusing host {:?}",
host_ptr
);
ns_window.makeKeyAndOrderFront(None);
ns_window.orderFrontRegardless();
}
}
}
}
if focus {
self.handle_focus_gained(window_id);
} else {
self.handle_focus_lost(window_id);
}
}
self.ui_state >= UIState::FirstFrame && should_render
}
#[cfg(target_os = "macos")]
fn sync_native_tabs_resize(&mut self, source_window_id: WindowId) {
if !native_tab_bar_enabled() || !self.settings.get::<CmdLineSettings>().system_native_tabs {
return;
}
if self.routes.len() <= 1 {
return;
}
let Some(source_route) = self.routes.get(&source_window_id) else {
return;
};
let shared_inner_size = source_route.window.winit_window.inner_size();
let window_ids: Vec<WindowId> = self.routes.keys().copied().collect();
for window_id in window_ids {
if let Some(route) = self.routes.get_mut(&window_id) {
if let Some(macos_feature) = &mut route.window.macos_feature {
macos_feature.borrow_mut().handle_size_changed();
}
}
let window_padding = self.calculate_window_padding(window_id);
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.saved_inner_size = shared_inner_size;
route.state.window_padding = window_padding;
route.window.last_synced_grid_size = None;
}
self.update_grid_size_from_window(window_id);
}
}
fn apply_message_selection_event(
&mut self,
window_id: WindowId,
action: MessageSelectionEvent,
) -> bool {
let Some(route) = self.routes.get(&window_id) else {
return false;
};
match action {
MessageSelectionEvent::Outside => false,
MessageSelectionEvent::Clear => {
route.window.renderer.borrow_mut().set_message_selection(None);
true
}
MessageSelectionEvent::Update(selection) => {
route.window.renderer.borrow_mut().set_message_selection(Some(selection));
true
}
MessageSelectionEvent::Finish(selection) => {
self.copy_message_selection(window_id, selection);
route.window.renderer.borrow_mut().set_message_selection(None);
true
}
}
}
fn copy_message_selection(&self, window_id: WindowId, selection: MessageSelection) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let renderer = route.window.renderer.borrow();
let Some(window) = renderer.rendered_windows.get(&selection.grid_id) else {
return;
};
let height = window.grid_size.height;
let width = window.grid_size.width;
if height == 0 || width == 0 {
return;
}
let max_row = height - 1;
let max_col = width - 1;
let start_row = selection.start.y.min(max_row);
let end_row = selection.end.y.min(max_row);
let (row_start, row_end) =
if start_row <= end_row { (start_row, end_row) } else { (end_row, start_row) };
let start_col = selection.start.x.min(max_col);
let end_col = selection.end.x.min(max_col);
let (col_start, col_end) =
if start_col <= end_col { (start_col, end_col) } else { (end_col, start_col) };
let mut lines = Vec::new();
for row in row_start..=row_end {
let row_start_col = if row == row_start { col_start } else { 0 };
let row_end_col = if row == row_end { col_end } else { max_col };
if let Some(line) = window.line_text_range(row, row_start_col, row_end_col) {
lines.push(line);
}
}
if lines.is_empty() || lines.iter().all(|line| line.is_empty()) {
return;
}
let text = lines.join("\n");
if let Some(clipboard) = self.clipboard.upgrade() {
if let Ok(mut clipboard) = clipboard.lock() {
#[cfg(target_os = "linux")]
let _ = clipboard.set_contents(text.clone(), "*");
let _ = clipboard.set_contents(text, "+");
}
}
}
pub fn handle_user_event(&mut self, event: EventPayload) {
let EventPayload { payload, target } = event;
let needs_window = matches!(payload, UserEvent::SettingsChanged(_));
if needs_window && !self.has_routes_for_target(target) {
return;
}
match payload {
UserEvent::DrawCommandBatch(batch) => match target {
EventTarget::Window(window_id) => self.handle_draw_commands(window_id, batch),
EventTarget::Route(route_id) => {
self.handle_draw_commands_for_route(route_id, batch)
}
_ => log::warn!("DrawCommandBatch event missing window/route target"),
},
UserEvent::WindowCommand(e) => {
self.handle_window_command(target, e);
}
UserEvent::SettingsChanged(SettingsChanged::Window(e)) => {
self.handle_window_settings_changed(target, e);
}
UserEvent::SettingsChanged(SettingsChanged::Renderer(e)) => {
self.handle_render_settings_changed(target, e);
}
#[cfg(target_os = "macos")]
UserEvent::MacShortcut(command) => {
self.handle_mac_shortcut(command);
}
UserEvent::ShowProgressBar { percent, .. } => {
self.handle_progress_bar(target, percent);
}
_ => {}
}
}
pub fn clear_renderer(&mut self, window_id: WindowId) {
if let Some(route) = self.routes.get(&window_id) {
route.window.renderer.borrow_mut().clear();
}
}
#[cfg(target_os = "macos")]
pub fn handle_mac_shortcut(&mut self, command: MacShortcutCommand) {
match command {
MacShortcutCommand::TogglePinnedWindow => self.toggle_pinned_window(),
MacShortcutCommand::ShowEditorSwitcher => self.show_editor_switcher(),
}
}
#[cfg(target_os = "macos")]
fn record_window_usage(&mut self, window_id: WindowId) {
self.window_mru.retain(|id| *id != window_id);
self.window_mru.push_front(window_id);
}
#[cfg(target_os = "macos")]
fn cleanup_window_mru(&mut self) {
self.window_mru.retain(|id| self.routes.contains_key(id));
}
#[cfg(target_os = "macos")]
fn handle_focus_gain_for_shortcuts(&mut self, window_id: WindowId) {
if self.ignore_next_focus_gain {
self.ignore_next_focus_gain = false;
} else {
self.record_window_usage(window_id);
}
}
#[cfg(target_os = "macos")]
fn pinned_candidate(&mut self) -> Option<WindowId> {
self.cleanup_window_mru();
if let Some(id) = self.window_mru.front().copied() {
Some(id)
} else {
self.routes.keys().next().copied()
}
}
#[cfg(target_os = "macos")]
fn capture_focus_target(&mut self, pinned_id: WindowId) {
let current = self.get_focused_route();
if current != Some(pinned_id) {
self.focus_return_target = current;
} else {
self.focus_return_target = None;
}
}
#[cfg(target_os = "macos")]
fn restore_focus_target(&mut self) -> bool {
let Some(target_id) = self.focus_return_target.take() else {
return false;
};
let Some(window) =
self.routes.get(&target_id).map(|route| route.window.winit_window.clone())
else {
return false;
};
if let Some(feature) = self.macos_feature_for_window(target_id) {
feature.borrow().activate_application();
}
window.focus_window();
true
}
#[cfg(target_os = "macos")]
fn toggle_pinned_window(&mut self) {
let Some(window_id) = self.pinned_candidate() else {
return;
};
let Some(route) = self.routes.get(&window_id) else {
return;
};
let window = route.window.winit_window.clone();
let macos_feature = self.macos_feature_for_window(window_id);
let is_active = {
if let Some(feature) = &macos_feature {
feature.borrow().is_key_window()
} else {
window.has_focus()
}
};
self.ignore_next_focus_gain = true;
if is_active {
let uses_native_tabs = native_tab_bar_enabled()
&& self.settings.get::<CmdLineSettings>().system_native_tabs;
if uses_native_tabs {
hide_application();
return;
}
if !self.restore_focus_target() {
hide_application();
}
} else {
if let Some(feature) = &macos_feature {
feature.borrow().activate_application();
}
#[cfg(target_os = "macos")]
self.capture_focus_target(window_id);
window.focus_window();
self.record_window_usage(window_id);
}
}
#[cfg(target_os = "macos")]
fn show_editor_switcher(&mut self) {
if is_tab_overview_active() {
trigger_tab_overview();
return;
}
let window_count = self.routes.len();
if window_count == 0 {
return;
}
if window_count == 1 {
self.toggle_pinned_window();
return;
}
#[cfg(target_os = "macos")]
{
let mut opened_overview = false;
if let Some(window_id) = self.pinned_candidate() {
if let Some(feature_rc) = self.macos_feature_for_window(window_id) {
{
let feature = feature_rc.borrow();
if feature.is_simple_fullscreen_enabled() {
drop(feature);
self.toggle_pinned_window();
return;
}
}
feature_rc.borrow().activate_application();
opened_overview = true;
}
}
if opened_overview {
trigger_tab_overview();
} else {
self.toggle_pinned_window();
}
}
}
pub fn draw_frame(&mut self, window_id: WindowId, dt: f32) {
tracy_zone!("draw_frame");
let content_rect = self.get_content_pixel_rect_from_window(window_id);
let Some(route) = self.routes.get_mut(&window_id) else {
return;
};
let mut renderer = route.window.renderer.borrow_mut();
let window = route.window.winit_window.clone();
let mut skia_renderer = route.window.skia_renderer.borrow_mut();
let Some(vsync) = route.state.vsync.as_mut() else {
return;
};
renderer.draw_frame(skia_renderer.canvas(), Some(&content_rect), dt);
skia_renderer.flush();
{
tracy_gpu_zone!("wait for vsync");
vsync.wait_for_vsync();
}
skia_renderer.swap_buffers();
if self.ui_state == UIState::FirstFrame {
window.set_visible(true);
self.ui_state = UIState::Showing;
}
tracy_frame();
tracy_gpu_collect();
}
pub fn refresh_rate_for_window(&self, window_id: WindowId, settings: &Settings) -> Option<f32> {
let route = self.routes.get(&window_id)?;
let vsync = route.state.vsync.as_ref()?;
Some(vsync.get_refresh_rate(&route.window.winit_window, settings))
}
pub fn request_redraw_for_window(&mut self, window_id: WindowId) -> Option<bool> {
let route = self.routes.get_mut(&window_id)?;
let vsync = route.state.vsync.as_mut()?;
if vsync.uses_winit_throttling() {
vsync.request_redraw(&route.window.winit_window);
Some(true)
} else {
Some(false)
}
}
pub fn animate_frame(&mut self, window_id: WindowId, dt: f32) -> bool {
tracy_zone!("animate_frame", 0);
let route = match self.routes.get(&window_id) {
Some(route) => route,
None => return false,
};
let mut renderer = route.window.renderer.borrow_mut();
let grid_scale = renderer.grid_renderer.grid_scale;
let res = renderer.animate_frame(
&self.get_grid_rect_from_window(window_id, grid_scale, GridSize::default()),
dt,
);
tracy_plot!("animate_frame", res as u8 as f64);
renderer.prepare_lines(false);
#[allow(clippy::let_and_return)]
res
}
pub fn try_create_window(
&mut self,
event_loop: &ActiveEventLoop,
proxy: &EventLoopProxy<EventPayload>,
cwd: Option<&Path>,
args: Option<OpenArgs>,
) {
let creating_initial_window = self.routes.is_empty();
let route_id = if creating_initial_window {
if self.ui_state != UIState::WaitingForWindowCreate {
return;
}
let Some(route_id) = self.pending_window_creation_route.take() else {
return;
};
route_id
} else {
RouteId::next()
};
tracy_zone!("try_create_window");
let persisted_window_settings = load_last_window_settings().ok();
let mut desired_window_size =
determine_window_size(persisted_window_settings.as_ref(), &self.settings.clone());
let mut desired_grid_size =
determine_grid_size(&desired_window_size, persisted_window_settings);
#[cfg(target_os = "macos")]
let mut host_window_position: Option<winit::dpi::PhysicalPosition<i32>> = None;
if !self.routes.is_empty() {
if let Some(host_id) = self.get_focused_route() {
if let Some(host_route) = self.routes.get(&host_id) {
desired_window_size =
WindowSize::Size(host_route.window.last_applied_window_size);
desired_grid_size = host_route.window.last_synced_grid_size.or_else(|| {
let renderer = host_route.window.renderer.borrow();
Some(renderer.get_grid_size())
});
#[cfg(target_os = "macos")]
{
host_window_position = host_route.window.winit_window.outer_position().ok();
}
}
}
}
let initial_inferred_theme = if creating_initial_window {
self.route_cores.get(&route_id).and_then(|route_core| route_core.inferred_theme)
} else {
None
};
let theme = self.get_theme_for(initial_inferred_theme);
let maximized = matches!(desired_window_size, WindowSize::Maximized);
let window_config = create_window(event_loop, maximized, "Neovide", &self.settings, theme);
let window = Rc::new(window_config.window.clone());
let mut route_title = String::from("Neovide");
let mut route_last_synced_grid_size = None;
let mut route_inferred_theme = None;
let mut route_mouse_enabled = true;
let mut should_apply_initial_window_size = false;
let mut route_font_changed_last_frame = false;
let WindowSettings {
input_ime,
opacity,
normal_opacity,
window_blurred,
fullscreen,
#[cfg(target_os = "macos")]
input_macos_option_key_is_meta,
#[cfg(target_os = "macos")]
macos_simple_fullscreen,
#[cfg(target_os = "windows")]
corner_preference,
#[cfg(target_os = "windows")]
title_background_color,
#[cfg(target_os = "windows")]
title_text_color,
..
} = self.settings.get::<WindowSettings>();
let (renderer, neovim_handler, route_pending_initial_window_size, route_cwd) =
if creating_initial_window {
let Some(route_core) = self.route_cores.remove(&route_id) else {
log::warn!("Missing pending route core for initial route {route_id:?}");
return;
};
debug_assert_eq!(route_core.route_id, route_id);
route_title = route_core.title;
route_last_synced_grid_size = route_core.last_synced_grid_size;
route_inferred_theme = route_core.inferred_theme;
route_mouse_enabled = route_core.mouse_enabled;
should_apply_initial_window_size = route_core.should_show_observed;
route_font_changed_last_frame = route_core.font_changed_last_frame;
(
route_core.renderer,
route_core.neovim_handler,
route_core.pending_initial_window_size,
route_core.cwd,
)
} else {
let config = Config::init();
let renderer = Rc::new(RefCell::new(Box::new(Renderer::new(
1.0,
config.clone(),
self.settings.clone(),
))));
let runtime =
self.runtime.as_mut().expect("Neovim runtime has not been initialized");
let neovim_handler = match runtime.launch(
route_id,
proxy.clone(),
desired_grid_size,
self.runtime_tracker.clone(),
self.settings.clone(),
&config,
cwd,
args.map_or(OpenMode::None, OpenMode::Args),
) {
Ok(handler) => handler,
Err(err) => {
let msg = format!("Failed to launch neovim runtime: {err:?}");
log::error!("{msg}");
let _ = proxy.send_event(EventPayload::all(UserEvent::NeovimLaunchError {
message: msg,
}));
return;
}
};
(renderer, neovim_handler, None, cwd.map(Path::to_path_buf))
};
window.set_ime_allowed(input_ime);
let scale_factor = window.scale_factor();
{
let mut renderer_ref = renderer.borrow_mut();
renderer_ref.sync_scale_factor();
renderer_ref.handle_os_scale_factor_change(scale_factor);
}
let mut pending_initial_window_size = None;
let mut initial_pixel_size: Option<PhysicalSize<u32>> = None;
match desired_window_size.clone() {
WindowSize::Maximized => {}
WindowSize::Grid(_) | WindowSize::NeovimGrid => {
pending_initial_window_size = Some(desired_window_size.clone());
}
WindowSize::Size(window_size) => {
initial_pixel_size = Some(window_size);
}
};
if pending_initial_window_size.is_none() {
pending_initial_window_size = route_pending_initial_window_size;
}
if !maximized {
if let Some(size) = initial_pixel_size {
tracy_zone!("request_inner_size");
let _ = window.request_inner_size(size);
}
}
#[cfg(target_os = "macos")]
if let Some(position) = host_window_position {
window.set_outer_position(position);
}
if let Ok(previous_position) = window.outer_position() {
if let Some(current_monitor) = window.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;
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);
};
};
}
let logged_size = initial_pixel_size.unwrap_or_default();
log::info!("Showing window size: {logged_size:#?}, maximized: {maximized}");
let is_wayland = match window.window_handle() {
Ok(handle) => matches!(handle.as_raw(), RawWindowHandle::Wayland(_)),
Err(err) => {
log::warn!("Failed to read window handle: {err}");
false
}
};
if is_wayland {
window.set_visible(true);
}
let cmd_line_settings = self.settings.get::<CmdLineSettings>();
let srgb = cmd_line_settings.srgb;
let vsync_enabled = cmd_line_settings.vsync;
let skia_renderer: Rc<RefCell<Box<dyn SkiaRenderer>>> = Rc::new(RefCell::new(
create_skia_renderer(&window_config, srgb, vsync_enabled, self.settings.clone()),
));
let window = skia_renderer.borrow_mut().window();
window.set_title(&route_title);
#[cfg(target_os = "windows")]
{
window.set_corner_preference(corner_preference.into());
if let Some(winit_color) = Self::parse_winit_color(&title_background_color) {
window.set_title_background_color(Some(winit_color));
}
if let Some(winit_color) = Self::parse_winit_color(&title_text_color) {
window.set_title_text_color(winit_color);
}
}
let saved_inner_size = window.inner_size();
log::info!(
"window created (scale_factor: {:.4}, font_dimensions: {:?})",
scale_factor,
renderer.borrow().grid_renderer.grid_scale
);
window.set_blur(window_blurred && opacity.min(normal_opacity) < 1.0);
#[cfg(target_os = "windows")]
if window_blurred {
window.set_system_backdrop(BackdropType::TransientWindow); }
if fullscreen {
let handle = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(handle)));
}
#[cfg(target_os = "windows")]
{
if let Some(winit_color) = Self::parse_winit_color(&title_background_color) {
window.set_title_background_color(Some(winit_color));
}
if let Some(winit_color) = Self::parse_winit_color(&title_text_color) {
window.set_title_text_color(winit_color);
}
}
let skia_renderer_ref: &dyn SkiaRenderer = &**skia_renderer.borrow();
let vsync =
VSync::new(vsync_enabled, skia_renderer_ref, proxy.clone(), self.settings.clone());
#[cfg(target_os = "macos")]
let macos_feature = {
let feature = MacosWindowFeature::from_winit_window(
&window,
self.settings.clone(),
proxy.clone(),
neovim_handler.clone(),
);
if creating_initial_window {
feature.activate_and_focus();
}
feature
};
if creating_initial_window {
self.ui_state = if should_apply_initial_window_size {
UIState::FirstFrame
} else {
UIState::Initing
};
}
let mouse_manager = MouseManager::new(self.settings.clone());
let mut mouse_manager = mouse_manager;
mouse_manager.enabled = route_mouse_enabled;
let mut state = RouteState::new();
state.saved_inner_size = saved_inner_size;
state.vsync = Some(vsync);
state.inferred_theme = route_inferred_theme;
state.font_changed_last_frame = route_font_changed_last_frame;
let route = Route {
route_id,
window: RouteWindow {
renderer,
skia_renderer: skia_renderer.clone(),
winit_window: window.clone(),
neovim_handler,
mouse_manager: Rc::new(RefCell::new(Box::new(mouse_manager))),
#[cfg(target_os = "macos")]
macos_feature: Some(Rc::new(RefCell::new(Box::new(macos_feature)))),
title: route_title,
last_applied_window_size: saved_inner_size,
last_synced_grid_size: route_last_synced_grid_size,
},
cwd: route_cwd,
pending_initial_window_size,
state,
};
self.routes.insert(window.id(), route);
self.apply_theme_for_window(window.id());
set_active_route_handler(route_id);
#[cfg(target_os = "macos")]
self.record_window_usage(window.id());
#[cfg(target_os = "macos")]
self.set_macos_option_as_meta(window.id(), input_macos_option_key_is_meta);
#[cfg(target_os = "macos")]
self.set_simple_fullscreen(window.id(), macos_simple_fullscreen);
if should_apply_initial_window_size {
self.apply_pending_initial_window_size(window.id());
}
if !creating_initial_window {
window.set_visible(true);
#[cfg(target_os = "macos")]
if let Some(feature) = self.macos_feature_for_window(window.id()) {
feature.borrow().activate_and_focus();
}
}
{
tracy_zone!("request_redraw");
window.request_redraw();
}
}
pub fn handle_draw_commands(&mut self, window_id: WindowId, batch: Vec<DrawCommand>) {
tracy_zone!("handle_draw_commands");
let Some(route) = self.routes.get(&window_id) else {
return;
};
let handle_draw_commands_result = {
let mut renderer = route.window.renderer.borrow_mut();
renderer.handle_draw_commands(batch)
};
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.font_changed_last_frame |= handle_draw_commands_result.font_changed;
}
if self.ui_state == UIState::Initing && handle_draw_commands_result.should_show {
log::info!("Showing the Window");
self.ui_state = UIState::FirstFrame;
}
if handle_draw_commands_result.should_show {
self.apply_pending_initial_window_size(window_id);
}
}
pub fn handle_draw_commands_for_route(&mut self, route_id: RouteId, batch: Vec<DrawCommand>) {
if let Some(window_id) = self.window_id_for_route(route_id) {
self.handle_draw_commands(window_id, batch);
return;
}
let Some(route_core) = self.route_cores.get_mut(&route_id) else {
return;
};
let handle_draw_commands_result = {
let mut renderer = route_core.renderer.borrow_mut();
renderer.handle_draw_commands(batch)
};
route_core.font_changed_last_frame |= handle_draw_commands_result.font_changed;
if handle_draw_commands_result.should_show {
route_core.should_show_observed = true;
if self.ui_state == UIState::Initing {
log::info!("Route ready for initial window creation");
self.pending_window_creation_route = Some(route_id);
self.ui_state = UIState::WaitingForWindowCreate;
}
}
}
fn apply_pending_initial_window_size(&mut self, window_id: WindowId) {
let pending = match self.routes.get(&window_id) {
Some(route) => match &route.pending_initial_window_size {
Some(value) => value.clone(),
None => return,
},
None => return,
};
let window = match self.routes.get(&window_id) {
Some(route) => route.window.winit_window.clone(),
None => return,
};
match pending {
WindowSize::Grid(grid_size) => {
let window_size = self.get_window_size_from_grid(window_id, &grid_size);
let _ = window
.request_inner_size(PhysicalSize::new(window_size.width, window_size.height));
}
WindowSize::NeovimGrid => {
let grid_size = match self.routes.get(&window_id) {
Some(route) => {
let renderer = route.window.renderer.borrow();
renderer.get_grid_size()
}
None => return,
};
let window_size = self.get_window_size_from_grid(window_id, &grid_size);
let _ = window
.request_inner_size(PhysicalSize::new(window_size.width, window_size.height));
}
WindowSize::Size(size) => {
let _ = window.request_inner_size(size);
}
WindowSize::Maximized => {
window.set_maximized(true);
}
}
if let Some(route) = self.routes.get_mut(&window_id) {
route.pending_initial_window_size = None;
}
}
pub fn queue_restart_route(&mut self, route_id: RouteId, details: RestartDetails) {
let grid_size = if let Some(window_id) = self.window_id_for_route(route_id) {
let grid_size = match self.routes.get(&window_id) {
Some(route) => route.window.renderer.borrow().get_grid_size(),
None => return,
};
self.clear_renderer(window_id);
if let Some(route) = self.routes.get_mut(&window_id) {
route.window.last_synced_grid_size = None;
}
grid_size
} else {
let Some(route_core) = self.route_cores.get_mut(&route_id) else {
return;
};
let grid_size = route_core.renderer.borrow().get_grid_size();
route_core.renderer.borrow_mut().clear();
route_core.last_synced_grid_size = None;
grid_size
};
self.pending_restart.insert(route_id, RestartRequest { details, grid_size });
}
fn restart_neovim_route(
&mut self,
route_id: RouteId,
restart: RestartRequest,
proxy: &EventLoopProxy<EventPayload>,
) -> Result<(), ()> {
let handler = self.neovim_handler_for_route(route_id).ok_or(())?;
let cwd = self.route_cwd(route_id);
let runtime = self.runtime.as_mut().ok_or(())?;
runtime
.restart(
route_id,
proxy.clone(),
handler,
restart.grid_size,
self.settings.clone(),
restart.details,
cwd.as_deref(),
)
.map_err(|error| {
log::error!("Failed to restart Neovim: {error:?}");
})
}
fn route_cwd(&self, route_id: RouteId) -> Option<PathBuf> {
if let Some(window_id) = self.window_id_for_route(route_id) {
return self.routes.get(&window_id).and_then(|route| route.cwd.clone());
}
self.route_cores.get(&route_id).and_then(|route_core| route_core.cwd.clone())
}
pub fn handle_neovim_exit(
&mut self,
window_id: WindowId,
proxy: &EventLoopProxy<EventPayload>,
) {
if let Some(route_id) = self.route_id_for_window(window_id) {
if let Some(restart) = self.pending_restart.remove(&route_id) {
if self.restart_neovim_route(route_id, restart, proxy).is_ok() {
return;
}
}
}
if let Some(route) = self.routes.remove(&window_id) {
let window = route.window.winit_window.clone();
window.set_visible(false);
#[cfg(target_os = "macos")]
{
self.window_mru.retain(|id| *id != window_id);
if self.focus_return_target == Some(window_id) {
self.focus_return_target = None;
}
}
drop(route);
}
if self.routes.is_empty() {
self.ui_state = UIState::Initing;
}
}
pub fn handle_neovim_exit_route(
&mut self,
route_id: RouteId,
proxy: &EventLoopProxy<EventPayload>,
) {
if let Some(window_id) = self.window_id_for_route(route_id) {
self.handle_neovim_exit(window_id, proxy);
if self.window_id_for_route(route_id).is_none() {
unregister_route_handler(route_id);
}
return;
}
if let Some(restart) = self.pending_restart.remove(&route_id) {
if self.restart_neovim_route(route_id, restart, proxy).is_ok() {
return;
}
}
self.route_cores.remove(&route_id);
unregister_route_handler(route_id);
if self.pending_window_creation_route == Some(route_id) {
self.pending_window_creation_route = None;
}
if self.routes.is_empty() && self.route_cores.is_empty() {
self.ui_state = UIState::Initing;
}
}
pub fn handle_window_config_changed(&mut self, config: WindowHotReloadConfigs) {
match config {
WindowHotReloadConfigs::TitleHidden(title_hidden) => {
self.handle_config_title_hidden_changed(title_hidden);
}
WindowHotReloadConfigs::MouseCursorIcon(mouse_cursor_icon) => {
self.handle_config_mouse_cursor_icon_changed(mouse_cursor_icon);
}
WindowHotReloadConfigs::Geometry(geometry) => {
self.handle_config_geometry_changed(geometry);
}
}
}
pub fn handle_renderer_config_changed(&mut self, config: RendererHotReloadConfigs) {
let Some(route) = self.focused_route_mut() else {
return;
};
let mut renderer = route.window.renderer.borrow_mut();
renderer.handle_config_changed(config);
route.state.font_changed_last_frame = true;
}
fn handle_config_title_hidden_changed(&mut self, title_hidden: Option<bool>) {
let title_hidden = title_hidden.unwrap_or(false);
let mut cmd_line_settings = self.settings.get::<CmdLineSettings>();
if cmd_line_settings.title_hidden == title_hidden {
return;
}
cmd_line_settings.title_hidden = title_hidden;
self.settings.set(&cmd_line_settings);
#[cfg(target_os = "macos")]
{
let window_ids: Vec<WindowId> = self.routes.keys().copied().collect();
for window_id in window_ids {
if let Some(macos_feature) = self.macos_feature_for_window(window_id) {
macos_feature.borrow_mut().set_title_hidden(title_hidden);
}
}
}
}
fn handle_config_mouse_cursor_icon_changed(&mut self, mouse_cursor_icon: MouseCursorIcon) {
let mut cmd_line_settings = self.settings.get::<CmdLineSettings>();
if cmd_line_settings.mouse_cursor_icon == mouse_cursor_icon {
return;
}
cmd_line_settings.mouse_cursor_icon = mouse_cursor_icon.clone();
self.settings.set(&cmd_line_settings);
let cursor = Cursor::Icon(mouse_cursor_icon.parse());
for route in self.routes.values() {
route.window.winit_window.set_cursor(cursor.clone());
}
}
fn handle_config_geometry_changed(&mut self, geometry: GeometryArgs) {
let Some(previous_geometry) = self.update_shared_geometry(&geometry) else {
return;
};
let window_ids: Vec<WindowId> = self.routes.keys().copied().collect();
if Self::should_exit_maximized_state(&previous_geometry, &geometry) {
self.set_windows_maximized(&window_ids, false);
}
match Self::geometry_target(geometry) {
GeometryTarget::Maximized => self.set_windows_maximized(&window_ids, true),
GeometryTarget::Size(size) => self.request_window_size_for(&window_ids, size),
GeometryTarget::Grid(grid_size) => self.request_grid_size_for(&window_ids, grid_size),
GeometryTarget::None => {}
}
}
fn update_shared_geometry(&self, geometry: &GeometryArgs) -> Option<GeometryArgs> {
let mut cmd_line_settings = self.settings.get::<CmdLineSettings>();
if cmd_line_settings.geometry == *geometry {
return None;
}
let previous_geometry = cmd_line_settings.geometry.clone();
cmd_line_settings.geometry = geometry.clone();
self.settings.set(&cmd_line_settings);
Some(previous_geometry)
}
fn should_exit_maximized_state(
previous_geometry: &GeometryArgs,
geometry: &GeometryArgs,
) -> bool {
!geometry.maximized
&& (previous_geometry.maximized
|| geometry.size.is_some()
|| matches!(geometry.grid, Some(Some(_))))
}
fn geometry_target(geometry: GeometryArgs) -> GeometryTarget {
if geometry.maximized {
return GeometryTarget::Maximized;
}
if let Some(size) = geometry.size {
return GeometryTarget::Size(PhysicalSize::from(size));
}
let Some(Some(dimensions)) = geometry.grid else {
return GeometryTarget::None;
};
GeometryTarget::Grid(clamped_grid_size(&GridSize::new(
dimensions.width.try_into().unwrap(),
dimensions.height.try_into().unwrap(),
)))
}
fn set_windows_maximized(&self, window_ids: &[WindowId], maximized: bool) {
for window_id in window_ids {
if let Some(route) = self.routes.get(window_id) {
route.window.winit_window.set_maximized(maximized);
}
}
}
fn request_window_size_for(&self, window_ids: &[WindowId], size: PhysicalSize<u32>) {
for window_id in window_ids {
if let Some(route) = self.routes.get(window_id) {
let _ = route.window.winit_window.request_inner_size(size);
}
}
}
fn request_grid_size_for(&mut self, window_ids: &[WindowId], grid_size: GridSize<u32>) {
for &window_id in window_ids {
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.requested_columns = Some(grid_size.width);
route.state.requested_lines = Some(grid_size.height);
}
self.update_window_size_from_grid(window_id);
}
}
fn handle_progress_bar(&mut self, target: EventTarget, percent: f32) {
tracy_zone!("handle_progress_bar");
let window_ids = self.window_ids_for_target(target);
for window_id in window_ids {
if let Some(route) = self.routes.get(&window_id) {
let mut renderer = route.window.renderer.borrow_mut();
renderer.progress_bar.start(percent);
}
}
}
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
fn calculate_window_padding(&self, window_id: WindowId) -> WindowPadding {
let window_settings = self.settings.get::<WindowSettings>();
#[cfg(not(target_os = "macos"))]
let window_padding_top = window_settings.padding_top;
#[cfg(target_os = "macos")]
let window_padding_top = {
let mut padding_top = window_settings.padding_top;
if let Some(macos_feature) = self.macos_feature_for_window(window_id) {
padding_top += macos_feature.borrow().extra_titlebar_height_in_pixels();
}
padding_top
};
WindowPadding {
top: window_padding_top,
left: window_settings.padding_left,
right: window_settings.padding_right,
bottom: window_settings.padding_bottom,
}
}
#[cfg(target_os = "macos")]
pub fn grid_to_pixel_position(
&mut self,
window_id: WindowId,
col: i64,
row: i64,
) -> Point2<Pixel<f32>> {
let grid_position = GridPos::new(col, row);
let mut renderer = {
let route = self.routes.get(&window_id).expect("window must exist");
route.window.renderer.borrow_mut()
};
let root_region_offset = renderer.window_regions.first().map(|region| region.region.min);
let grid_scale = renderer.grid_renderer.grid_scale;
let baseline_offset = renderer.grid_renderer.shaper.baseline_offset();
drop(renderer);
let mut position = grid_position * grid_scale;
if let Some(offset) = root_region_offset {
position.x += offset.x;
position.y += offset.y;
}
position.y += baseline_offset;
position
}
#[cfg(target_os = "macos")]
pub fn apply_padding_to_position(
&self,
position: Point2<Pixel<f32>>,
padding: WindowPadding,
titlebar_height: f32,
) -> Point2<Pixel<f32>> {
let _ = (padding, titlebar_height);
position
}
pub fn get_focused_route(&self) -> Option<WindowId> {
if let Some(id) = self.routes.iter().find_map(|(key, val)| {
if (!val.window.winit_window.has_focus() && self.routes.len() == 1)
|| val.window.winit_window.has_focus()
{
Some(*key)
} else {
None
}
}) {
return Some(id);
}
#[cfg(target_os = "macos")]
{
if let Some(id) = self
.window_mru
.iter()
.copied()
.find(|candidate| self.routes.contains_key(candidate))
{
return Some(id);
}
}
self.routes.keys().next().copied()
}
#[cfg(target_os = "macos")]
pub fn activate_and_focus_window(&self, window_id: WindowId) -> bool {
let Some(feature) = self.macos_feature_for_window(window_id) else {
return false;
};
feature.borrow().activate_and_focus();
true
}
pub fn window_id_for_route(&self, route_id: RouteId) -> Option<WindowId> {
self.routes
.iter()
.find_map(|(window_id, route)| (route.route_id == route_id).then_some(*window_id))
}
pub fn route_id_for_window(&self, window_id: WindowId) -> Option<RouteId> {
self.routes.get(&window_id).map(|route| route.route_id)
}
fn neovim_handler_for_route(&self, route_id: RouteId) -> Option<NeovimHandler> {
self.window_id_for_route(route_id)
.and_then(|window_id| {
self.routes.get(&window_id).map(|route| route.window.neovim_handler.clone())
})
.or_else(|| {
self.route_cores.get(&route_id).map(|route_core| route_core.neovim_handler.clone())
})
}
#[cfg(target_os = "macos")]
fn macos_feature_for_window(
&self,
window_id: WindowId,
) -> Option<Rc<RefCell<Box<MacosWindowFeature>>>> {
self.routes.get(&window_id).and_then(|route| route.window.macos_feature.as_ref().cloned())
}
fn resolve_target_window_id(&self, target: EventTarget) -> Option<WindowId> {
match target {
EventTarget::Focused | EventTarget::All => self.get_focused_route(),
EventTarget::Route(route_id) => self.window_id_for_route(route_id),
EventTarget::Window(window_id) => {
self.routes.contains_key(&window_id).then_some(window_id)
}
}
}
fn window_ids_for_target(&self, target: EventTarget) -> Vec<WindowId> {
match target {
EventTarget::All => self.routes.keys().copied().collect(),
EventTarget::Focused => self.get_focused_route().into_iter().collect(),
EventTarget::Route(route_id) => {
self.window_id_for_route(route_id).into_iter().collect()
}
EventTarget::Window(window_id) => {
self.routes.contains_key(&window_id).then_some(window_id).into_iter().collect()
}
}
}
fn focused_route(&self) -> Option<&Route> {
self.resolve_target_window_id(EventTarget::Focused).and_then(|id| self.routes.get(&id))
}
fn focused_route_mut(&mut self) -> Option<&mut Route> {
let id = self.resolve_target_window_id(EventTarget::Focused)?;
self.routes.get_mut(&id)
}
fn has_routes_for_target(&self, target: EventTarget) -> bool {
self.resolve_target_window_id(target).is_some()
}
pub fn prepare_frame(&mut self, window_id: WindowId) -> ShouldRender {
tracy_zone!("prepare_frame", 0);
if !self.routes.contains_key(&window_id) {
return ShouldRender::Wait;
}
let mut should_render = ShouldRender::Wait;
let window_padding = self.calculate_window_padding(window_id);
let padding_changed = self
.routes
.get(&window_id)
.map(|route| route.state.window_padding != window_padding)
.unwrap_or(false);
if self.ui_state < UIState::FirstFrame {
return ShouldRender::Wait;
} else if self.ui_state == UIState::FirstFrame {
should_render = ShouldRender::Immediately;
}
let is_minimized = self
.routes
.get(&window_id)
.map(|route| route.window.winit_window.is_minimized() == Some(true))
.unwrap_or(false);
let resize_requested = self
.routes
.get(&window_id)
.map(|route| {
route.state.requested_columns.is_some() || route.state.requested_lines.is_some()
})
.unwrap_or(false);
if resize_requested {
self.update_window_size_from_grid(window_id);
} else if !is_minimized {
let new_window_size = match self.routes.get(&window_id) {
Some(route) => route.window.winit_window.inner_size(),
None => return should_render,
};
let mut needs_window_update = false;
if let Some(route) = self.routes.get(&window_id) {
if route.state.saved_inner_size != new_window_size
|| route.state.font_changed_last_frame
|| padding_changed
|| route.window.last_applied_window_size != route.state.saved_inner_size
{
needs_window_update = true;
}
}
if needs_window_update {
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.window_padding = window_padding;
route.state.saved_inner_size = new_window_size;
}
self.update_grid_size_from_window(window_id);
if let Some(route) = self.routes.get_mut(&window_id) {
route.window.last_applied_window_size = route.state.saved_inner_size;
}
should_render = ShouldRender::Immediately;
}
}
self.update_ime_position(window_id, false);
if let Some(route) = self.routes.get(&window_id) {
let mut renderer = route.window.renderer.borrow_mut();
should_render.update(renderer.prepare_frame());
}
if let Some(route) = self.routes.get_mut(&window_id) {
if route.state.font_changed_last_frame {
let mut renderer = route.window.renderer.borrow_mut();
renderer.prepare_lines(true);
route.state.font_changed_last_frame = false;
}
}
should_render
}
pub fn get_grid_size(&self) -> GridSize<u32> {
let Some(route) = self.focused_route() else {
return DEFAULT_GRID_SIZE;
};
let renderer = route.window.renderer.borrow();
renderer.get_grid_size()
}
fn get_window_size_from_grid(
&self,
window_id: WindowId,
grid_size: &GridSize<u32>,
) -> PixelSize<u32> {
let window_padding = self.calculate_window_padding(window_id);
let window_padding_size = PixelSize::new(
window_padding.left + window_padding.right,
window_padding.top + window_padding.bottom,
);
let round_or_ceil = |v: PixelSize<f32>| -> PixelSize<f32> {
PixelSize::new(round_or_op(v.width, f32::ceil), round_or_op(v.height, f32::ceil))
};
let Some(route) = self.routes.get(&window_id) else {
return PixelSize::new(0, 0);
};
let renderer = route.window.renderer.borrow();
let window_size = round_or_ceil(*grid_size * renderer.grid_renderer.grid_scale)
.try_cast()
.unwrap_or_default()
+ window_padding_size;
log::info!(
"get_window_size_from_grid: Grid Size: {grid_size:?}, Window Size {window_size:?}"
);
window_size
}
fn update_window_size_from_grid(&mut self, window_id: WindowId) {
let grid_size = {
let Some(route) = self.routes.get_mut(&window_id) else {
return;
};
clamped_grid_size(&GridSize::new(
route.state.requested_columns.take().unwrap_or(
route.state.saved_grid_size.map_or(DEFAULT_GRID_SIZE.width, |v| v.width),
),
route.state.requested_lines.take().unwrap_or(
route.state.saved_grid_size.map_or(DEFAULT_GRID_SIZE.height, |v| v.height),
),
))
};
let new_size = self.get_window_size_from_grid(window_id, &grid_size);
let window = match self.routes.get(&window_id) {
Some(route) => route.window.winit_window.clone(),
None => return,
};
let new_size = winit::dpi::PhysicalSize { width: new_size.width, height: new_size.height };
let _ = window.request_inner_size(new_size);
if let Some(route) = self.routes.get(&window_id) {
let mut skia_renderer = route.window.skia_renderer.borrow_mut();
skia_renderer.resize();
}
}
fn get_grid_size_from_window(
&self,
window_id: WindowId,
grid_scale: GridScale,
min: GridSize<u32>,
) -> GridSize<u32> {
let route = match self.routes.get(&window_id) {
Some(route) => route,
None => return min,
};
let window_padding = route.state.window_padding;
let window_padding_size: PixelSize<u32> = PixelSize::new(
window_padding.left + window_padding.right,
window_padding.top + window_padding.bottom,
);
let content_size =
PixelSize::new(route.state.saved_inner_size.width, route.state.saved_inner_size.height)
- window_padding_size;
let round_or_floor = |v: GridSize<f32>| -> GridSize<f32> {
GridSize::new(round_or_op(v.width, f32::floor), round_or_op(v.height, f32::floor))
};
let grid_size = round_or_floor(content_size / grid_scale).try_cast().unwrap_or(min);
grid_size.max(min)
}
fn get_grid_rect_from_window(
&self,
window_id: WindowId,
grid_scale: GridScale,
min: GridSize<u32>,
) -> GridRect<f32> {
let size = self
.get_grid_size_from_window(window_id, grid_scale, min)
.try_cast()
.unwrap_or_default();
let pos = self
.routes
.get(&window_id)
.map(|route| {
PixelPos::new(route.state.window_padding.left, route.state.window_padding.top)
.cast()
/ grid_scale
})
.unwrap_or_else(|| PixelPos::new(0, 0).cast() / grid_scale);
GridRect::<f32>::from_origin_and_size(pos, size)
}
fn get_content_pixel_rect_from_window(&self, window_id: WindowId) -> PixelRect<f32> {
let Some(route) = self.routes.get(&window_id) else {
return PixelRect::new(PixelPos::new(0.0, 0.0), PixelPos::new(0.0, 0.0));
};
let window_padding = route.state.window_padding;
let window_padding_size: PixelSize<u32> = PixelSize::new(
window_padding.left + window_padding.right,
window_padding.top + window_padding.bottom,
);
let content_size =
PixelSize::new(route.state.saved_inner_size.width, route.state.saved_inner_size.height)
- window_padding_size;
let min = PixelPos::new(window_padding.left as f32, window_padding.top as f32);
let max = PixelPos::new(
(window_padding.left + content_size.width) as f32,
(window_padding.top + content_size.height) as f32,
);
PixelRect::new(min, max)
}
fn update_grid_size_from_window(&mut self, window_id: WindowId) {
let (grid_scale, neovim_handler, last_synced, saved_inner_size) =
match self.routes.get(&window_id) {
Some(route) => {
let renderer = route.window.renderer.borrow();
(
renderer.grid_renderer.grid_scale,
route.window.neovim_handler.clone(),
route.window.last_synced_grid_size,
route.state.saved_inner_size,
)
}
None => return,
};
let grid_size = self.get_grid_size_from_window(window_id, grid_scale, MIN_GRID_SIZE);
if last_synced.as_ref() == Some(&grid_size) {
trace!("Grid matched route size, skip update.");
return;
}
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.saved_grid_size = Some(grid_size);
}
log::info!(
"Resizing grid based on window size. Grid Size: {:?}, Window Size {:?}",
grid_size,
saved_inner_size
);
send_ui(
ParallelCommand::Resize {
width: grid_size.width.into(),
height: grid_size.height.into(),
},
&neovim_handler,
);
if let Some(route_mut) = self.routes.get_mut(&window_id) {
route_mut.window.last_synced_grid_size = Some(grid_size);
}
}
fn update_ime_position(&mut self, window_id: WindowId, force: bool) {
let (window, grid_scale, position, current_area) = {
let route = match self.routes.get(&window_id) {
Some(route) => route,
None => return,
};
if !route.state.ime_enabled {
return;
}
let window = route.window.winit_window.clone();
let renderer = route.window.renderer.borrow();
let grid_scale = renderer.grid_renderer.grid_scale;
let position = renderer.get_cursor_destination();
let position = match position.try_cast::<u32>() {
Some(position) => position,
None => return,
};
let position = dpi::PhysicalPosition { x: position.x, y: position.y };
(window, grid_scale, position, route.state.ime_area)
};
let font_dimensions = GridSize::new(1.0, 1.0) * grid_scale;
let width = (font_dimensions.width * 2.0).ceil() as u32;
let height = font_dimensions.height.ceil() as u32;
let size = dpi::PhysicalSize::new(width, height);
let area = (position, size);
if force || current_area != area {
if let Some(route) = self.routes.get_mut(&window_id) {
route.state.ime_area = (position, size);
}
window.set_ime_cursor_area(position, size);
}
}
fn handle_scale_factor_update(&mut self, window_id: WindowId, scale_factor: f64) {
#[cfg(target_os = "macos")]
let macos_feature = self.macos_feature_for_window(window_id);
let Some(route) = self.routes.get(&window_id) else {
return;
};
let mut renderer = route.window.renderer.borrow_mut();
let mut skia_renderer = route.window.skia_renderer.borrow_mut();
#[cfg(target_os = "macos")]
{
if let Some(macos_feature) = macos_feature {
macos_feature.borrow_mut().handle_scale_factor_update(scale_factor);
}
}
renderer.handle_os_scale_factor_change(scale_factor);
skia_renderer.resize();
}
#[cfg(windows)]
fn parse_winit_color(color: &str) -> Option<Color> {
match csscolorparser::parse(color) {
Ok(color) => {
let color = color.to_rgba8();
Some(Color::from_rgb(color[0], color[1], color[2]))
}
_ => None,
}
}
#[cfg(windows)]
fn handle_title_background_color(&self, window_id: WindowId, color: &str) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let skia_renderer = route.window.skia_renderer.borrow();
let winit_color = Self::parse_winit_color(color);
skia_renderer.window().set_title_background_color(winit_color);
}
#[cfg(windows)]
fn handle_title_text_color(&self, window_id: WindowId, color: &str) {
let Some(route) = self.routes.get(&window_id) else {
return;
};
let skia_renderer = route.window.skia_renderer.borrow();
if let Some(winit_color) = Self::parse_winit_color(color) {
skia_renderer.window().set_title_text_color(winit_color);
}
}
}