/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A winit window implementation. use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::env; use std::rc::Rc; use std::time::Duration; use euclid::{Angle, Length, Point2D, Rotation3D, Scale, Size2D, UnknownUnit, Vector2D, Vector3D}; use keyboard_types::{Modifiers, ShortcutMatcher}; use log::{debug, info}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use servo::compositing::windowing::{ AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods, }; use servo::config::opts::Opts; use servo::servo_config::pref; use servo::servo_geometry::DeviceIndependentPixel; use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel}; use servo::webrender_api::ScrollLocation; use servo::webrender_traits::SurfmanRenderingContext; use servo::{ Cursor, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Theme, TouchEvent, TouchEventAction, TouchId, WebView, WheelDelta, WheelEvent, WheelMode, }; use surfman::{Context, Device, SurfaceType}; use url::Url; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{ ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, }; use winit::event_loop::ActiveEventLoop; use winit::keyboard::{Key as LogicalKey, ModifiersState, NamedKey}; #[cfg(any(target_os = "linux", target_os = "windows"))] use winit::window::Icon; use super::app_state::RunningAppState; use super::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size}; use super::keyutils::{keyboard_event_from_winit, CMD_OR_ALT}; use super::window_trait::{WindowPortsMethods, LINE_HEIGHT}; use crate::desktop::keyutils::CMD_OR_CONTROL; use crate::prefs::ServoShellPreferences; pub struct Window { winit_window: winit::window::Window, screen_size: Size2D, inner_size: Cell>, toolbar_height: Cell>, mouse_down_button: Cell>, mouse_down_point: Cell>, monitor: winit::monitor::MonitorHandle, mouse_pos: Cell>, last_pressed: Cell)>>, /// A map of winit's key codes to key values that are interpreted from /// winit's ReceivedChar events. keys_down: RefCell>, animation_state: Cell, fullscreen: Cell, device_pixel_ratio_override: Option, xr_window_poses: RefCell>>, modifiers_state: Cell, } impl Window { pub fn new( opts: &Opts, servoshell_preferences: &ServoShellPreferences, rendering_context: &SurfmanRenderingContext, event_loop: &ActiveEventLoop, ) -> Window { // If there's no chrome, start off with the window invisible. It will be set to visible in // `load_end()`. This avoids an ugly flash of unstyled content (especially important since // unstyled content is white and chrome often has a transparent background). See issue // #9996. let no_native_titlebar = servoshell_preferences.no_native_titlebar; let visible = opts.output_file.is_none() && !servoshell_preferences.no_native_titlebar; let window_size = servoshell_preferences.initial_window_size; let window_attr = winit::window::Window::default_attributes() .with_title("Servo".to_string()) .with_decorations(!no_native_titlebar) .with_transparent(no_native_titlebar) .with_inner_size(LogicalSize::new(window_size.width, window_size.height)) .with_visible(visible); #[allow(deprecated)] let winit_window = event_loop .create_window(window_attr) .expect("Failed to create window."); #[cfg(any(target_os = "linux", target_os = "windows"))] { let icon_bytes = include_bytes!("../../../resources/servo_64.png"); winit_window.set_window_icon(Some(load_icon(icon_bytes))); } let monitor = winit_window .current_monitor() .or_else(|| winit_window.available_monitors().nth(0)) .expect("No monitor detected"); let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else( || (monitor.size(), monitor.scale_factor()), |size| (PhysicalSize::new(size.width, size.height), 1.0), ); let screen_scale: Scale = Scale::new(screen_scale); let screen_size = (winit_size_to_euclid_size(screen_size).to_f64() / screen_scale).to_u32(); // Initialize surfman let window_handle = winit_window .window_handle() .expect("could not get window handle from window"); let inner_size = winit_window.inner_size(); let native_widget = rendering_context .connection() .create_native_widget_from_window_handle( window_handle, winit_size_to_euclid_size(inner_size).to_i32().to_untyped(), ) .expect("Failed to create native widget"); let surface_type = SurfaceType::Widget { native_widget }; let surface = rendering_context .create_surface(surface_type) .expect("Failed to create surface"); rendering_context .bind_surface(surface) .expect("Failed to bind surface"); // Make sure the gl context is made current. rendering_context.make_gl_context_current().unwrap(); debug!("Created window {:?}", winit_window.id()); Window { winit_window, mouse_down_button: Cell::new(None), mouse_down_point: Cell::new(Point2D::zero()), mouse_pos: Cell::new(Point2D::zero()), last_pressed: Cell::new(None), keys_down: RefCell::new(HashMap::new()), animation_state: Cell::new(AnimationState::Idle), fullscreen: Cell::new(false), inner_size: Cell::new(inner_size), monitor, screen_size, device_pixel_ratio_override: servoshell_preferences.device_pixel_ratio_override, xr_window_poses: RefCell::new(vec![]), modifiers_state: Cell::new(ModifiersState::empty()), toolbar_height: Cell::new(Default::default()), } } fn handle_received_character(&self, webview: &WebView, mut character: char) { info!("winit received character: {:?}", character); if character.is_control() { if character as u8 >= 32 { return; } // shift ASCII control characters to lowercase character = (character as u8 + 96) as char; } let (mut event, key_code) = if let Some((event, key_code)) = self.last_pressed.replace(None) { (event, key_code) } else if character.is_ascii() { // Some keys like Backspace emit a control character in winit // but they are already dealt with in handle_keyboard_input // so just ignore the character. return; } else { // For combined characters like the letter e with an acute accent // no keyboard event is emitted. A dummy event is created in this case. (KeyboardEvent::default(), None) }; event.key = Key::Character(character.to_string()); if event.state == KeyState::Down { // Ensure that when we receive a keyup event from winit, we are able // to infer that it's related to this character and set the event // properties appropriately. if let Some(key_code) = key_code { self.keys_down .borrow_mut() .insert(key_code, event.key.clone()); } } let xr_poses = self.xr_window_poses.borrow(); for xr_window_pose in &*xr_poses { xr_window_pose.handle_xr_translation(&event); } webview.notify_input_event(InputEvent::Keyboard(event)); } fn handle_keyboard_input(&self, state: Rc, winit_event: KeyEvent) { // First, handle servoshell key bindings that are not overridable by, or visible to, the page. let mut keyboard_event = keyboard_event_from_winit(&winit_event, self.modifiers_state.get()); if self.handle_intercepted_key_bindings(state.clone(), &keyboard_event) { return; } // Then we deliver character and keyboard events to the page in the focused webview. let Some(webview) = state.focused_webview() else { return; }; if let Some(input_text) = &winit_event.text { for character in input_text.chars() { self.handle_received_character(&webview, character); } } if keyboard_event.state == KeyState::Down && keyboard_event.key == Key::Unidentified { // If pressed and probably printable, we expect a ReceivedCharacter event. // Wait for that to be received and don't queue any event right now. self.last_pressed .set(Some((keyboard_event, Some(winit_event.logical_key)))); return; } else if keyboard_event.state == KeyState::Up && keyboard_event.key == Key::Unidentified { // If release and probably printable, this is following a ReceiverCharacter event. if let Some(key) = self.keys_down.borrow_mut().remove(&winit_event.logical_key) { keyboard_event.key = key; } } if keyboard_event.key != Key::Unidentified { self.last_pressed.set(None); let xr_poses = self.xr_window_poses.borrow(); for xr_window_pose in &*xr_poses { xr_window_pose.handle_xr_rotation(&winit_event, self.modifiers_state.get()); } webview.notify_input_event(InputEvent::Keyboard(keyboard_event)); } // servoshell also has key bindings that are visible to, and overridable by, the page. // See the handler for EmbedderMsg::Keyboard in webview.rs for those. } /// Helper function to handle a click fn handle_mouse( &self, webview: &WebView, button: MouseButton, action: ElementState, coords: Point2D, ) { let max_pixel_dist = 10.0 * self.hidpi_factor().get(); let mouse_button = match &button { MouseButton::Left => ServoMouseButton::Left, MouseButton::Right => ServoMouseButton::Right, MouseButton::Middle => ServoMouseButton::Middle, MouseButton::Back => ServoMouseButton::Back, MouseButton::Forward => ServoMouseButton::Forward, MouseButton::Other(value) => ServoMouseButton::Other(*value), }; let action = match action { ElementState::Pressed => { self.mouse_down_point.set(coords); self.mouse_down_button.set(Some(button)); MouseButtonAction::Down }, ElementState::Released => MouseButtonAction::Up, }; webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent { action, button: mouse_button, point: coords.to_f32(), })); // Also send a 'click' event if this is release and the press was recorded // to be within a 10 pixels. // // TODO: This should be happening within the ScriptThread. if action != MouseButtonAction::Up { return; } if let Some(mouse_down_button) = self.mouse_down_button.get() { let pixel_dist = self.mouse_down_point.get() - coords; let pixel_dist = ((pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y) as f32).sqrt(); if mouse_down_button == button && pixel_dist < max_pixel_dist { webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent { action: MouseButtonAction::Click, button: mouse_button, point: coords.to_f32(), })); } } } /// Handle key events before sending them to Servo. fn handle_intercepted_key_bindings( &self, state: Rc, key_event: &KeyboardEvent, ) -> bool { let Some(focused_webview) = state.focused_webview() else { return false; }; let mut handled = true; ShortcutMatcher::from_event(key_event.clone()) .shortcut(CMD_OR_CONTROL, 'R', || focused_webview.reload()) .shortcut(CMD_OR_CONTROL, 'W', || { state.close_webview(focused_webview.id()); }) .shortcut(CMD_OR_CONTROL, 'P', || { let rate = env::var("SAMPLING_RATE") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(10); let duration = env::var("SAMPLING_DURATION") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(10); focused_webview.toggle_sampling_profiler( Duration::from_millis(rate), Duration::from_secs(duration), ); }) .shortcut(CMD_OR_CONTROL, 'X', || { focused_webview .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Cut)) }) .shortcut(CMD_OR_CONTROL, 'C', || { focused_webview .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Copy)) }) .shortcut(CMD_OR_CONTROL, 'V', || { focused_webview .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Paste)) }) .shortcut(Modifiers::CONTROL, Key::F9, || { focused_webview.capture_webrender(); }) .shortcut(Modifiers::CONTROL, Key::F10, || { focused_webview.toggle_webrender_debugging(WebRenderDebugOption::RenderTargetDebug); }) .shortcut(Modifiers::CONTROL, Key::F11, || { focused_webview.toggle_webrender_debugging(WebRenderDebugOption::TextureCacheDebug); }) .shortcut(Modifiers::CONTROL, Key::F12, || { focused_webview.toggle_webrender_debugging(WebRenderDebugOption::Profiler); }) .shortcut(CMD_OR_ALT, Key::ArrowRight, || { focused_webview.go_forward(1); }) .optional_shortcut( cfg!(not(target_os = "windows")), CMD_OR_CONTROL, ']', || { focused_webview.go_forward(1); }, ) .shortcut(CMD_OR_ALT, Key::ArrowLeft, || { focused_webview.go_back(1); }) .optional_shortcut( cfg!(not(target_os = "windows")), CMD_OR_CONTROL, '[', || { focused_webview.go_back(1); }, ) .optional_shortcut( self.get_fullscreen(), Modifiers::empty(), Key::Escape, || focused_webview.exit_fullscreen(), ) // Select the first 8 tabs via shortcuts .shortcut(CMD_OR_CONTROL, '1', || state.focus_webview_by_index(0)) .shortcut(CMD_OR_CONTROL, '2', || state.focus_webview_by_index(1)) .shortcut(CMD_OR_CONTROL, '3', || state.focus_webview_by_index(2)) .shortcut(CMD_OR_CONTROL, '4', || state.focus_webview_by_index(3)) .shortcut(CMD_OR_CONTROL, '5', || state.focus_webview_by_index(4)) .shortcut(CMD_OR_CONTROL, '6', || state.focus_webview_by_index(5)) .shortcut(CMD_OR_CONTROL, '7', || state.focus_webview_by_index(6)) .shortcut(CMD_OR_CONTROL, '8', || state.focus_webview_by_index(7)) // Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th .shortcut(CMD_OR_CONTROL, '9', || { let len = state.webviews().len(); if len > 0 { state.focus_webview_by_index(len - 1) } }) .shortcut(Modifiers::CONTROL, Key::PageDown, || { if let Some(index) = state.get_focused_webview_index() { state.focus_webview_by_index((index + 1) % state.webviews().len()) } }) .shortcut(Modifiers::CONTROL, Key::PageUp, || { if let Some(index) = state.get_focused_webview_index() { let new_index = if index == 0 { state.webviews().len() - 1 } else { index - 1 }; state.focus_webview_by_index(new_index) } }) .shortcut(CMD_OR_CONTROL, 'T', || { state.new_toplevel_webview(Url::parse("servo:newtab").unwrap()); }) .shortcut(CMD_OR_CONTROL, 'Q', || state.servo().start_shutting_down()) .otherwise(|| handled = false); handled } } impl WindowPortsMethods for Window { fn device_hidpi_factor(&self) -> Scale { Scale::new(self.winit_window.scale_factor() as f32) } fn device_pixel_ratio_override( &self, ) -> Option> { self.device_pixel_ratio_override.map(Scale::new) } fn page_height(&self) -> f32 { let dpr = self.hidpi_factor(); let size = self.winit_window.inner_size(); size.height as f32 * dpr.get() } fn set_title(&self, title: &str) { self.winit_window.set_title(title); } fn request_resize(&self, _: &WebView, size: DeviceIntSize) -> Option { let toolbar_height = self.toolbar_height() * self.hidpi_factor(); let toolbar_height = toolbar_height.get().ceil() as i32; let total_size = PhysicalSize::new(size.width, size.height + toolbar_height); self.winit_window .request_inner_size::>(PhysicalSize::new( total_size.width, total_size.height, )) .and_then(|size| { Some(DeviceIntSize::new( size.width.try_into().ok()?, size.height.try_into().ok()?, )) }) } fn set_position(&self, point: DeviceIntPoint) { self.winit_window .set_outer_position::>(PhysicalPosition::new(point.x, point.y)) } fn set_fullscreen(&self, state: bool) { if self.fullscreen.get() != state { self.winit_window.set_fullscreen(if state { Some(winit::window::Fullscreen::Borderless(Some( self.monitor.clone(), ))) } else { None }); } self.fullscreen.set(state); } fn get_fullscreen(&self) -> bool { self.fullscreen.get() } fn set_cursor(&self, cursor: Cursor) { use winit::window::CursorIcon; let winit_cursor = match cursor { Cursor::Default => CursorIcon::Default, Cursor::Pointer => CursorIcon::Pointer, Cursor::ContextMenu => CursorIcon::ContextMenu, Cursor::Help => CursorIcon::Help, Cursor::Progress => CursorIcon::Progress, Cursor::Wait => CursorIcon::Wait, Cursor::Cell => CursorIcon::Cell, Cursor::Crosshair => CursorIcon::Crosshair, Cursor::Text => CursorIcon::Text, Cursor::VerticalText => CursorIcon::VerticalText, Cursor::Alias => CursorIcon::Alias, Cursor::Copy => CursorIcon::Copy, Cursor::Move => CursorIcon::Move, Cursor::NoDrop => CursorIcon::NoDrop, Cursor::NotAllowed => CursorIcon::NotAllowed, Cursor::Grab => CursorIcon::Grab, Cursor::Grabbing => CursorIcon::Grabbing, Cursor::EResize => CursorIcon::EResize, Cursor::NResize => CursorIcon::NResize, Cursor::NeResize => CursorIcon::NeResize, Cursor::NwResize => CursorIcon::NwResize, Cursor::SResize => CursorIcon::SResize, Cursor::SeResize => CursorIcon::SeResize, Cursor::SwResize => CursorIcon::SwResize, Cursor::WResize => CursorIcon::WResize, Cursor::EwResize => CursorIcon::EwResize, Cursor::NsResize => CursorIcon::NsResize, Cursor::NeswResize => CursorIcon::NeswResize, Cursor::NwseResize => CursorIcon::NwseResize, Cursor::ColResize => CursorIcon::ColResize, Cursor::RowResize => CursorIcon::RowResize, Cursor::AllScroll => CursorIcon::AllScroll, Cursor::ZoomIn => CursorIcon::ZoomIn, Cursor::ZoomOut => CursorIcon::ZoomOut, Cursor::None => { self.winit_window.set_cursor_visible(false); return; }, }; self.winit_window.set_cursor(winit_cursor); self.winit_window.set_cursor_visible(true); } fn is_animating(&self) -> bool { self.animation_state.get() == AnimationState::Animating } fn id(&self) -> winit::window::WindowId { self.winit_window.id() } fn handle_winit_event(&self, state: Rc, event: WindowEvent) { let Some(webview) = state.focused_webview() else { return; }; match event { WindowEvent::KeyboardInput { event, .. } => self.handle_keyboard_input(state, event), WindowEvent::ModifiersChanged(modifiers) => self.modifiers_state.set(modifiers.state()), WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left || button == MouseButton::Right { self.handle_mouse(&webview, button, state, self.mouse_pos.get()); } }, WindowEvent::CursorMoved { position, .. } => { let position = winit_position_to_euclid_point(position); self.mouse_pos.set(position.to_i32()); webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent { point: position.to_f32(), })); }, WindowEvent::MouseWheel { delta, phase, .. } => { let (mut dx, mut dy, mode) = match delta { MouseScrollDelta::LineDelta(dx, dy) => { (dx as f64, (dy * LINE_HEIGHT) as f64, WheelMode::DeltaLine) }, MouseScrollDelta::PixelDelta(position) => { let scale_factor = self.device_hidpi_factor().inverse().get() as f64; let position = position.to_logical(scale_factor); (position.x, position.y, WheelMode::DeltaPixel) }, }; // Create wheel event before snapping to the major axis of movement let delta = WheelDelta { x: dx, y: dy, z: 0.0, mode, }; let pos = self.mouse_pos.get(); let point = Point2D::new(pos.x as f32, pos.y as f32); // Scroll events snap to the major axis of movement, with vertical // preferred over horizontal. if dy.abs() >= dx.abs() { dx = 0.0; } else { dy = 0.0; } let scroll_location = ScrollLocation::Delta(Vector2D::new(dx as f32, dy as f32)); let phase = winit_phase_to_touch_event_action(phase); // Send events webview.notify_input_event(InputEvent::Wheel(WheelEvent { delta, point })); webview.notify_scroll_event(scroll_location, self.mouse_pos.get(), phase); }, WindowEvent::Touch(touch) => { webview.notify_input_event(InputEvent::Touch(TouchEvent { action: winit_phase_to_touch_event_action(touch.phase), id: TouchId(touch.id as i32), point: Point2D::new(touch.location.x as f32, touch.location.y as f32), })); }, WindowEvent::PinchGesture { delta, .. } => { webview.set_pinch_zoom(delta as f32 + 1.0); }, WindowEvent::CloseRequested => { state.servo().start_shutting_down(); }, WindowEvent::Resized(new_size) => { if self.inner_size.get() != new_size { self.inner_size.set(new_size); webview.notify_rendering_context_resized(); } }, WindowEvent::ThemeChanged(theme) => { webview.notify_theme_change(match theme { winit::window::Theme::Light => Theme::Light, winit::window::Theme::Dark => Theme::Dark, }); }, WindowEvent::Moved(_new_position) => { webview.notify_embedder_window_moved(); }, _ => {}, } } fn new_glwindow( &self, event_loop: &ActiveEventLoop, ) -> Rc { let size = self.winit_window.outer_size(); let window_attr = winit::window::Window::default_attributes() .with_title("Servo XR".to_string()) .with_inner_size(size) .with_visible(false); let winit_window = event_loop .create_window(window_attr) .expect("Failed to create window."); let pose = Rc::new(XRWindowPose { xr_rotation: Cell::new(Rotation3D::identity()), xr_translation: Cell::new(Vector3D::zero()), }); self.xr_window_poses.borrow_mut().push(pose.clone()); Rc::new(XRWindow { winit_window, pose }) } fn winit_window(&self) -> Option<&winit::window::Window> { Some(&self.winit_window) } fn toolbar_height(&self) -> Length { self.toolbar_height.get() } fn set_toolbar_height(&self, height: Length) { self.toolbar_height.set(height); } } impl WindowMethods for Window { fn get_coordinates(&self) -> EmbedderCoordinates { let window_size = winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(); let window_origin = self.winit_window.outer_position().unwrap_or_default(); let window_origin = winit_position_to_euclid_point(window_origin).to_i32(); let window_rect = DeviceIntRect::from_origin_and_size(window_origin, window_size); let window_scale: Scale = Scale::new(self.winit_window.scale_factor()); let window_rect = (window_rect.to_f64() / window_scale).to_i32(); let viewport_origin = DeviceIntPoint::zero(); // bottom left let viewport_size = winit_size_to_euclid_size(self.winit_window.inner_size()).to_f32(); let viewport = DeviceIntRect::from_origin_and_size(viewport_origin, viewport_size.to_i32()); let screen_size = self.screen_size.to_i32(); EmbedderCoordinates { viewport, framebuffer: viewport.size(), window_rect, screen_size, // FIXME: Winit doesn't have API for available size. Fallback to screen size available_screen_size: screen_size, hidpi_factor: self.hidpi_factor(), } } fn set_animation_state(&self, state: AnimationState) { self.animation_state.set(state); } } fn winit_phase_to_touch_event_action(phase: TouchPhase) -> TouchEventAction { match phase { TouchPhase::Started => TouchEventAction::Down, TouchPhase::Moved => TouchEventAction::Move, TouchPhase::Ended => TouchEventAction::Up, TouchPhase::Cancelled => TouchEventAction::Cancel, } } #[cfg(any(target_os = "linux", target_os = "windows"))] fn load_icon(icon_bytes: &[u8]) -> Icon { let (icon_rgba, icon_width, icon_height) = { use image::{GenericImageView, Pixel}; let image = image::load_from_memory(icon_bytes).expect("Failed to load icon"); let (width, height) = image.dimensions(); let mut rgba = Vec::with_capacity((width * height) as usize * 4); for (_, _, pixel) in image.pixels() { rgba.extend_from_slice(&pixel.to_rgba().0); } (rgba, width, height) }; Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon") } struct XRWindow { winit_window: winit::window::Window, pose: Rc, } struct XRWindowPose { xr_rotation: Cell>, xr_translation: Cell>, } impl servo::webxr::glwindow::GlWindow for XRWindow { fn get_render_target( &self, device: &mut Device, _context: &mut Context, ) -> servo::webxr::glwindow::GlWindowRenderTarget { self.winit_window.set_visible(true); let window_handle = self .winit_window .window_handle() .expect("could not get window handle from window"); let size = self.winit_window.inner_size(); let size = Size2D::new(size.width as i32, size.height as i32); let native_widget = device .connection() .create_native_widget_from_window_handle(window_handle, size) .expect("Failed to create native widget"); servo::webxr::glwindow::GlWindowRenderTarget::NativeWidget(native_widget) } fn get_rotation(&self) -> Rotation3D { self.pose.xr_rotation.get() } fn get_translation(&self) -> Vector3D { self.pose.xr_translation.get() } fn get_mode(&self) -> servo::webxr::glwindow::GlWindowMode { if pref!(dom_webxr_glwindow_red_cyan) { servo::webxr::glwindow::GlWindowMode::StereoRedCyan } else if pref!(dom_webxr_glwindow_left_right) { servo::webxr::glwindow::GlWindowMode::StereoLeftRight } else if pref!(dom_webxr_glwindow_spherical) { servo::webxr::glwindow::GlWindowMode::Spherical } else if pref!(dom_webxr_glwindow_cubemap) { servo::webxr::glwindow::GlWindowMode::Cubemap } else { servo::webxr::glwindow::GlWindowMode::Blit } } fn display_handle(&self) -> raw_window_handle::DisplayHandle { self.winit_window.display_handle().unwrap() } } impl XRWindowPose { fn handle_xr_translation(&self, input: &KeyboardEvent) { if input.state != KeyState::Down { return; } const NORMAL_TRANSLATE: f32 = 0.1; const QUICK_TRANSLATE: f32 = 1.0; let mut x = 0.0; let mut z = 0.0; match input.key { Key::Character(ref k) => match &**k { "w" => z = -NORMAL_TRANSLATE, "W" => z = -QUICK_TRANSLATE, "s" => z = NORMAL_TRANSLATE, "S" => z = QUICK_TRANSLATE, "a" => x = -NORMAL_TRANSLATE, "A" => x = -QUICK_TRANSLATE, "d" => x = NORMAL_TRANSLATE, "D" => x = QUICK_TRANSLATE, _ => return, }, _ => return, }; let (old_x, old_y, old_z) = self.xr_translation.get().to_tuple(); let vec = Vector3D::new(x + old_x, old_y, z + old_z); self.xr_translation.set(vec); } fn handle_xr_rotation(&self, input: &KeyEvent, modifiers: ModifiersState) { if input.state != ElementState::Pressed { return; } let mut x = 0.0; let mut y = 0.0; match input.logical_key { LogicalKey::Named(NamedKey::ArrowUp) => x = 1.0, LogicalKey::Named(NamedKey::ArrowDown) => x = -1.0, LogicalKey::Named(NamedKey::ArrowLeft) => y = 1.0, LogicalKey::Named(NamedKey::ArrowRight) => y = -1.0, _ => return, }; if modifiers.shift_key() { x *= 10.0; y *= 10.0; } let x: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_x(Angle::degrees(x)); let y: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_y(Angle::degrees(y)); let rotation = self.xr_rotation.get().then(&x).then(&y); self.xr_rotation.set(rotation); } }