use std::{cell::Cell, collections::HashMap};
use base::id::WebViewId;
use constellation_traits::EmbedderToConstellationMessage;
use crossbeam_channel::Sender;
use embedder_traits::{
AlertResponse, AllowOrDeny, ConfirmResponse, Cursor, EmbedderMsg, ImeEvent, InputEvent,
MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Notification, PromptResponse,
TouchEventType, ViewportDetails, WebDriverJSValue, WebResourceResponseMsg, WheelMode,
};
use euclid::{Point2D, Scale, Size2D};
use glutin::{
config::{ConfigTemplateBuilder, GlConfig},
surface::{Surface, WindowSurface},
};
use glutin_winit::DisplayBuilder;
use ipc_channel::ipc::IpcSender;
use keyboard_types::{
Code, CompositionEvent, CompositionState, KeyState, KeyboardEvent, Modifiers,
};
#[cfg(any(target_os = "macos", target_os = "windows"))]
use muda::{MenuEvent, MenuEventReceiver};
#[cfg(linux)]
use notify_rust::Image;
#[cfg(target_os = "macos")]
use raw_window_handle::HasWindowHandle;
use reqwest::Client;
use servo_url::ServoUrl;
use versoview_messages::ToControllerMessage;
use webrender_api::{
ScrollLocation,
units::{DeviceIntPoint, DevicePoint, DeviceRect, DeviceSize, LayoutVector2D},
};
#[cfg(any(linux, target_os = "windows"))]
use winit::window::ResizeDirection;
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition},
event::{ElementState, Ime, TouchPhase, WindowEvent},
event_loop::ActiveEventLoop,
keyboard::ModifiersState,
window::{CursorIcon, Window as WinitWindow, WindowAttributes, WindowId},
};
use crate::{
bookmark::BookmarkManager,
compositor::IOCompositor,
keyboard::keyboard_event_from_winit,
rendering::{RenderingContext, gl_config_picker},
tab::TabManager,
verso::{VersoInternalMsg, send_to_constellation},
webview::{Panel, WebView, execute_script, prompt::PromptSender, webview_menu::WebViewMenu},
};
use arboard::Clipboard;
const PANEL_HEIGHT: f64 = 50.0;
const TAB_HEIGHT: f64 = 30.0;
const BOOKMARK_HEIGHT: f64 = 30.0;
const PANEL_PADDING: f64 = 4.0;
#[derive(Default)]
pub(crate) struct EventListeners {
pub(crate) on_navigation_starting: bool,
pub(crate) on_web_resource_requested:
Option<HashMap<uuid::Uuid, (url::Url, IpcSender<WebResourceResponseMsg>)>>,
pub(crate) on_close_requested: bool,
}
#[derive(Debug, Default)]
struct CursorState {
current_cursor: CursorIcon,
#[cfg(any(linux, target_os = "windows"))]
cursor_resizing: bool,
}
pub struct Window {
pub(crate) window: WinitWindow,
cursor_state: CursorState,
pub(crate) surface: Surface<WindowSurface>,
pub(crate) panel: Option<Panel>,
pub(crate) event_listeners: EventListeners,
pub(crate) mouse_position: Cell<Option<PhysicalPosition<f64>>>,
modifiers_state: Cell<ModifiersState>,
pub(crate) resizing: bool,
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub(crate) menu_event_receiver: MenuEventReceiver,
pub(crate) tab_manager: TabManager,
pub(crate) focused_webview_id: Option<WebViewId>,
pub(crate) webview_menu: Option<Box<dyn WebViewMenu>>,
pub show_bookmark: bool,
pub(crate) reqwest_client: Client,
pub(crate) verso_internal_sender: IpcSender<VersoInternalMsg>,
}
impl Window {
pub fn new(
evl: &ActiveEventLoop,
window_attributes: WindowAttributes,
verso_internal_sender: IpcSender<VersoInternalMsg>,
) -> (Self, RenderingContext) {
let template = ConfigTemplateBuilder::new()
.with_alpha_size(8)
.with_transparency(cfg!(macos));
let (window, gl_config) = DisplayBuilder::new()
.with_window_attributes(Some(window_attributes))
.build(evl, template, gl_config_picker)
.expect("Failed to create window and gl config");
let window = window.ok_or("Failed to create window").unwrap();
log::debug!("Picked a config with {} samples", gl_config.num_samples());
#[cfg(macos)]
unsafe {
let rwh = window.window_handle().expect("Failed to get window handle");
if let RawWindowHandle::AppKit(AppKitWindowHandle { ns_view, .. }) = rwh.as_ref() {
decorate_window(
ns_view.as_ptr() as *mut AnyObject,
LogicalPosition::new(8.0, 40.0),
);
}
}
let (rendering_context, surface) =
RenderingContext::create(&window, &gl_config, window.inner_size())
.expect("Failed to create rendering context");
log::trace!("Created rendering context for window {:?}", window);
(
Self {
window,
cursor_state: CursorState::default(),
surface,
panel: None,
event_listeners: Default::default(),
mouse_position: Default::default(),
modifiers_state: Cell::new(ModifiersState::default()),
resizing: false,
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEvent::receiver().clone(),
tab_manager: TabManager::new(),
focused_webview_id: None,
webview_menu: None,
show_bookmark: false,
reqwest_client: Client::new(),
verso_internal_sender,
},
rendering_context,
)
}
pub fn new_with_compositor(
evl: &ActiveEventLoop,
window_attributes: WindowAttributes,
compositor: &mut IOCompositor,
verso_internal_sender: IpcSender<VersoInternalMsg>,
) -> Self {
let window = evl
.create_window(window_attributes)
.expect("Failed to create window.");
#[cfg(macos)]
unsafe {
let rwh = window.window_handle().expect("Failed to get window handle");
if let RawWindowHandle::AppKit(AppKitWindowHandle { ns_view, .. }) = rwh.as_ref() {
decorate_window(
ns_view.as_ptr() as *mut AnyObject,
LogicalPosition::new(8.0, 40.0),
);
}
}
let surface = compositor
.rendering_context
.create_surface(&window)
.unwrap();
let mut window = Self {
window,
cursor_state: CursorState::default(),
surface,
panel: None,
event_listeners: Default::default(),
mouse_position: Default::default(),
modifiers_state: Cell::new(ModifiersState::default()),
resizing: false,
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEvent::receiver().clone(),
tab_manager: TabManager::new(),
focused_webview_id: None,
webview_menu: None,
show_bookmark: false,
reqwest_client: Client::new(),
verso_internal_sender,
};
compositor.swap_current_window(&mut window);
window
}
pub fn get_content_size(
&self,
mut size: DeviceRect,
include_tab: bool,
include_bookmark: bool,
) -> DeviceRect {
if self.panel.is_some() {
let mut height: f64 = PANEL_HEIGHT + PANEL_PADDING;
if include_tab {
height += TAB_HEIGHT;
}
if include_bookmark {
height += BOOKMARK_HEIGHT;
}
height *= self.scale_factor();
size.min.y = size.max.y.min(height as f32);
size.min.x += 10.0;
size.max.y -= 10.0;
size.max.x -= 10.0;
}
size
}
pub fn create_panel(
&mut self,
constellation_sender: &Sender<EmbedderToConstellationMessage>,
initial_url: url::Url,
) {
let hidpi_scale_factor = Scale::new(self.scale_factor() as f32);
let size = self.window.inner_size();
let size = Size2D::new(size.width as f32, size.height as f32);
let size = size.to_f32() / hidpi_scale_factor;
let viewport_details = ViewportDetails {
size,
hidpi_scale_factor,
};
let panel_id = WebViewId::new();
self.panel = Some(Panel {
webview: WebView::new(panel_id, viewport_details),
initial_url: ServoUrl::from_url(initial_url),
});
let url = ServoUrl::parse("verso://resources/components/panel.html").unwrap();
send_to_constellation(
constellation_sender,
EmbedderToConstellationMessage::NewWebView(url, panel_id, viewport_details),
);
}
pub fn create_tab(
&mut self,
constellation_sender: &Sender<EmbedderToConstellationMessage>,
initial_url: ServoUrl,
) {
let webview_id = WebViewId::new();
let size = self.size().to_f32();
let rect = DeviceRect::from_size(size);
let show_tab = self.tab_manager.count() >= 1;
let content_size = self.get_content_size(rect, show_tab, self.show_bookmark);
let hidpi_scale_factor = Scale::new(self.scale_factor() as f32);
let size = content_size.size().to_f32() / hidpi_scale_factor;
let viewport_details = ViewportDetails {
size,
hidpi_scale_factor,
};
let mut webview = WebView::new(webview_id, viewport_details);
webview.set_size(content_size);
if let Some(panel) = &self.panel {
let cmd: String = format!(
"window.navbar.addTab('{}', {})",
serde_json::to_string(&webview.webview_id).unwrap(),
true,
);
let _ = execute_script(constellation_sender, &panel.webview.webview_id, cmd);
}
self.tab_manager.append_tab(webview, true);
send_to_constellation(
constellation_sender,
EmbedderToConstellationMessage::NewWebView(initial_url, webview_id, viewport_details),
);
log::debug!("Verso Window {:?} adds webview {}", self.id(), webview_id);
}
pub fn close_tab(&mut self, compositor: &mut IOCompositor, tab_id: WebViewId) {
if self.tab_manager.count() > 1 {
if let Some(panel) = &self.panel {
let cmd: String = format!(
"window.navbar.closeTab('{}')",
serde_json::to_string(&tab_id).unwrap()
);
let active_tab_id = execute_script(
&compositor.constellation_chan,
&panel.webview.webview_id,
cmd,
)
.unwrap();
if let WebDriverJSValue::String(resp) = active_tab_id {
let active_id: WebViewId = serde_json::from_str(&resp).unwrap();
self.activate_tab(compositor, active_id, self.tab_manager.count() > 2);
}
}
}
send_to_constellation(
&compositor.constellation_chan,
EmbedderToConstellationMessage::CloseWebView(tab_id),
);
}
pub fn activate_tab(
&mut self,
compositor: &mut IOCompositor,
tab_id: WebViewId,
show_tab: bool,
) {
let size = self.size().to_f32();
let rect = DeviceRect::from_size(size);
let content_size = self.get_content_size(rect, show_tab, self.show_bookmark);
let (tab_id, prompt_id) = self.tab_manager.set_size(tab_id, content_size);
if let Some(prompt_id) = prompt_id {
compositor.on_resize_webview_event(prompt_id, content_size);
}
if let Some(tab_id) = tab_id {
compositor.on_resize_webview_event(tab_id, content_size);
let old_tab_id = self.tab_manager.current_tab_id();
if self.tab_manager.activate_tab(tab_id).is_some() {
if let Some(old_tab_id) = old_tab_id {
let _ = compositor.constellation_chan.send(
EmbedderToConstellationMessage::SetWebViewThrottled(old_tab_id, true),
);
}
let _ = compositor.constellation_chan.send(
EmbedderToConstellationMessage::SetWebViewThrottled(tab_id, false),
);
self.focused_webview_id = Some(tab_id);
let _ = compositor
.constellation_chan
.send(EmbedderToConstellationMessage::FocusWebView(tab_id));
let history = self.tab_manager.history(tab_id).unwrap();
let prev_btn_enabled = history.current_idx > 0;
let next_btn_enabled = history.current_idx < history.list.len() - 1;
let _ = execute_script(
&compositor.constellation_chan,
&self.panel.as_ref().unwrap().webview.webview_id,
format!(
"window.navbar.setNavBtnEnabled({}, {})",
prev_btn_enabled, next_btn_enabled
),
);
compositor.send_root_pipeline_display_list(self);
}
}
}
pub fn handle_winit_window_event(
&mut self,
sender: &Sender<EmbedderToConstellationMessage>,
compositor: &mut IOCompositor,
event: &winit::event::WindowEvent,
) {
match event {
WindowEvent::RedrawRequested => {
if compositor.ready_to_present {
self.window.pre_present_notify();
if let Err(err) = compositor.rendering_context.present(&self.surface) {
log::warn!("Failed to present surface: {:?}", err);
}
compositor.ready_to_present = false;
}
}
WindowEvent::Focused(focused) => {
if *focused {
compositor.swap_current_window(self);
}
}
WindowEvent::Resized(size) => {
if self.window.has_focus() {
self.resizing = true;
}
let size = Size2D::new(size.width, size.height);
compositor.resize(size.to_f32(), self);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
compositor.on_scale_factor_event(*scale_factor as f32, self);
}
WindowEvent::CursorEntered { .. } => {
compositor.swap_current_window(self);
}
WindowEvent::CursorLeft { .. } => {
self.mouse_position.set(None);
}
WindowEvent::CursorMoved { position, .. } => {
let point: DevicePoint = DevicePoint::new(position.x as f32, position.y as f32);
self.mouse_position.set(Some(*position));
let webview_id = match self.focused_webview_id {
Some(webview_id) => webview_id,
None => {
log::trace!("No focused webview, skipping MouseInput event.");
return;
}
};
forward_input_event(
compositor,
webview_id,
sender,
InputEvent::MouseMove(MouseMoveEvent { point }),
);
#[cfg(any(linux, target_os = "windows"))]
{
if self.should_use_client_region_drag() {
let direction = self.get_drag_resize_direction();
self.set_drag_resize_cursor(direction);
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
let point = match self.mouse_position.get() {
Some(point) => Point2D::new(point.x as f32, point.y as f32),
None => {
log::trace!("Mouse position is None, skipping MouseInput event.");
return;
}
};
if let (ElementState::Pressed, winit::event::MouseButton::Right) = (state, button) {
let prompt = self.tab_manager.current_prompt();
if prompt.is_some() {
return;
}
}
#[cfg(any(linux, target_os = "windows"))]
{
if *state == ElementState::Pressed
&& *button == winit::event::MouseButton::Left
&& self.should_use_client_region_drag()
{
self.drag_resize_window();
}
}
let button: MouseButton = match button {
winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle,
_ => {
log::trace!(
"Verso Window isn't supporting this mouse button yet: {button:?}"
);
return;
}
};
let event: MouseButtonEvent = match state {
ElementState::Pressed => MouseButtonEvent {
point,
action: MouseButtonAction::Down,
button,
},
ElementState::Released => {
self.resizing = false;
MouseButtonEvent {
point,
action: MouseButtonAction::Up,
button,
}
}
};
let Some(webview_id) = &compositor.webview_id_from_point(point) else {
log::trace!("No webview at point, skipping MouseInput event.");
return;
};
forward_input_event(
compositor,
*webview_id,
sender,
InputEvent::MouseButton(event),
);
if *state == ElementState::Released {
let event: MouseButtonEvent = MouseButtonEvent {
point,
action: MouseButtonAction::Click,
button,
};
forward_input_event(
compositor,
*webview_id,
sender,
InputEvent::MouseButton(event),
);
}
}
WindowEvent::PinchGesture { delta, .. } => {
compositor.on_zoom_window_event(1.0 + *delta as f32, self);
}
WindowEvent::MouseWheel { delta, phase, .. } => {
let point = match self.mouse_position.get() {
Some(point) => point,
None => {
log::trace!("Mouse position is None, skipping MouseWheel event.");
return;
}
};
const LINE_HEIGHT: f32 = 38.0;
let (mut x, mut y, _mode) = match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
(*x as f64, (*y * LINE_HEIGHT) as f64, WheelMode::DeltaLine)
}
winit::event::MouseScrollDelta::PixelDelta(position) => {
let position = position.to_logical::<f64>(self.window.scale_factor());
(position.x, position.y, WheelMode::DeltaPixel)
}
};
if y.abs() >= x.abs() {
x = 0.0;
} else {
y = 0.0;
}
let phase: TouchEventType = match phase {
TouchPhase::Started => TouchEventType::Down,
TouchPhase::Moved => TouchEventType::Move,
TouchPhase::Ended => TouchEventType::Up,
TouchPhase::Cancelled => TouchEventType::Cancel,
};
compositor.on_scroll_event(
ScrollLocation::Delta(LayoutVector2D::new(x as f32, y as f32)),
DeviceIntPoint::new(point.x as i32, point.y as i32),
phase,
);
}
WindowEvent::ModifiersChanged(modifier) => self.modifiers_state.set(modifier.state()),
WindowEvent::Ime(event) => {
let webview_id = match self.focused_webview_id {
Some(webview_id) => webview_id,
None => {
log::trace!("No focused webview, skipping Ime event.");
return;
}
};
if !self.has_webview(webview_id) {
log::trace!(
"Webview {:?} doesn't exist, skipping Ime event.",
webview_id
);
return;
}
match event {
Ime::Commit(text) => {
let text = text.clone();
forward_input_event(
compositor,
webview_id,
sender,
InputEvent::Ime(ImeEvent::Composition(CompositionEvent {
state: CompositionState::End,
data: text,
})),
);
}
Ime::Enabled => {
forward_input_event(
compositor,
webview_id,
sender,
InputEvent::Ime(ImeEvent::Composition(CompositionEvent {
state: CompositionState::Start,
data: String::new(),
})),
);
}
Ime::Preedit(text, _) => {
forward_input_event(
compositor,
webview_id,
sender,
InputEvent::Ime(ImeEvent::Composition(CompositionEvent {
state: CompositionState::Update,
data: text.to_string(),
})),
);
}
Ime::Disabled => {
forward_input_event(
compositor,
webview_id,
sender,
InputEvent::Ime(ImeEvent::Composition(CompositionEvent {
state: CompositionState::End,
data: String::new(),
})),
);
}
}
}
WindowEvent::KeyboardInput { event, .. } => {
let webview_id = match self.focused_webview_id {
Some(webview_id) => webview_id,
None => {
log::trace!("No focused webview, skipping KeyboardInput event.");
return;
}
};
if !self.has_webview(webview_id) {
log::trace!(
"Webview {:?} doesn't exist, skipping KeyboardInput event.",
webview_id
);
return;
}
let event = keyboard_event_from_winit(event, self.modifiers_state.get());
log::trace!("Verso is handling {:?}", event);
if self.handle_keyboard_shortcut(compositor, &event) {
return;
}
forward_input_event(compositor, webview_id, sender, InputEvent::Keyboard(event));
}
e => log::trace!("Verso Window isn't supporting this window event yet: {e:?}"),
}
}
fn handle_keyboard_shortcut(
&mut self,
compositor: &mut IOCompositor,
event: &KeyboardEvent,
) -> bool {
let is_macos = cfg!(target_os = "macos");
let control_or_meta = if is_macos {
Modifiers::META
} else {
Modifiers::CONTROL
};
if event.state == KeyState::Down {
match (event.modifiers, event.code) {
(modifiers, Code::KeyT) if modifiers == control_or_meta => {
(*self).create_tab(
&compositor.constellation_chan,
ServoUrl::parse("https://example.com").unwrap(),
);
return true;
}
(modifiers, Code::KeyW) if modifiers == control_or_meta => {
if let Some(tab_id) = self.tab_manager.current_tab_id() {
(*self).close_tab(compositor, tab_id);
}
return true;
}
(modifiers, Code::KeyL) if modifiers == control_or_meta => {
if let Some(panel) = &self.panel {
let webview_id = &panel.webview.webview_id;
let _ = compositor.constellation_chan.send(
EmbedderToConstellationMessage::FocusWebView(webview_id.clone()),
);
let _ = execute_script(
&compositor.constellation_chan,
webview_id,
"window.navbar.focusUrlInput()".to_string(),
);
}
return true;
}
_ => (),
}
}
false
}
pub fn handle_servo_message(
&mut self,
webview_id: WebViewId,
message: EmbedderMsg,
sender: Sender<EmbedderToConstellationMessage>,
to_controller_sender: &Option<IpcSender<ToControllerMessage>>,
clipboard: Option<&mut Clipboard>,
compositor: &mut IOCompositor,
bookmark_manager: &mut BookmarkManager,
) -> bool {
if let EmbedderMsg::SetCursor(_, cursor) = message {
self.set_cursor_icon(cursor);
return false;
}
if let Some(panel) = &self.panel {
if panel.webview.webview_id == webview_id {
return self.handle_servo_messages_with_panel(
webview_id,
message,
sender.clone(),
clipboard,
compositor,
bookmark_manager,
);
}
}
if let Some(webview_menu) = &self.webview_menu {
if webview_menu.webview().webview_id == webview_id {
self.handle_servo_messages_with_webview_menu(
webview_id, message, &sender, clipboard, compositor,
);
return false;
}
}
if self.tab_manager.has_prompt(webview_id) {
self.handle_servo_messages_with_prompt(
webview_id, message, &sender, clipboard, compositor,
);
return false;
}
self.handle_servo_messages_with_webview(
webview_id,
message,
&sender,
to_controller_sender,
clipboard,
compositor,
);
false
}
pub fn request_redraw(&self) {
self.window.request_redraw()
}
pub fn size(&self) -> DeviceSize {
let size = self.window.inner_size();
Size2D::new(size.width as f32, size.height as f32)
}
pub fn outer_size(&self) -> DeviceSize {
let size = self.window.outer_size();
Size2D::new(size.width as f32, size.height as f32)
}
pub fn id(&self) -> WindowId {
self.window.id()
}
pub fn scale_factor(&self) -> f64 {
self.window.scale_factor()
}
pub fn has_webview(&self, id: WebViewId) -> bool {
if self
.webview_menu
.as_ref()
.is_some_and(|w| w.webview().webview_id == id)
{
return true;
}
if self.tab_manager.has_prompt(id) {
return true;
}
if let Some(panel) = &self.panel {
if panel.webview.webview_id == id {
return true;
}
}
if self.tab_manager.tab(id).is_some() {
return true;
}
false
}
pub fn remove_webview(
&mut self,
id: WebViewId,
compositor: &mut IOCompositor,
) -> (Option<WebView>, bool) {
if self
.webview_menu
.as_ref()
.filter(|menu| menu.webview().webview_id == id)
.is_some()
{
let webview_menu = self.webview_menu.take().expect("Context menu should exist");
return (Some(webview_menu.webview().clone()), false);
}
if let Some(prompt) = self.tab_manager.remove_prompt_by_prompt_id(id) {
return (Some(prompt.webview().clone()), false);
}
if self
.panel
.as_ref()
.filter(|w| w.webview.webview_id == id)
.is_some()
{
let tab_ids = self.tab_manager.tab_ids();
for tab_id in tab_ids {
send_to_constellation(
&compositor.constellation_chan,
EmbedderToConstellationMessage::CloseWebView(tab_id),
);
}
(self.panel.take().map(|panel| panel.webview), false)
} else if let Ok(tab) = self.tab_manager.close_tab(id) {
let close_window = self.tab_manager.count() == 0 || self.panel.is_none();
if self.focused_webview_id == Some(id) {
self.focused_webview_id = None;
}
(Some(tab.webview().clone()), close_window)
} else {
(None, false)
}
}
pub fn painting_order(&self) -> Vec<&WebView> {
let mut order = vec![];
if let Some(panel) = &self.panel {
order.push(&panel.webview);
}
if let Some(tab) = self.tab_manager.current_tab() {
order.push(tab.webview());
}
if let Some(webview_menu) = &self.webview_menu {
order.push(webview_menu.webview());
}
if let Some(prompt) = self.tab_manager.current_prompt() {
order.push(prompt.webview());
}
order
}
pub fn set_cursor_icon(&mut self, cursor: Cursor) {
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.window.set_cursor_visible(false);
return;
}
};
self.cursor_state.current_cursor = winit_cursor;
self.window.set_cursor(winit_cursor);
self.window.set_cursor_visible(true);
}
pub fn show_ime(
&self,
_input_method_typee: embedder_traits::InputMethodType,
_text: Option<(String, i32)>,
_multilinee: bool,
position: euclid::Box2D<i32, webrender_api::units::DevicePixel>,
show_bookmark: bool,
) {
self.window.set_ime_allowed(true);
let mut height: f64 = PANEL_HEIGHT + PANEL_PADDING;
if self.tab_manager.count() > 1 {
height += TAB_HEIGHT;
}
if show_bookmark {
height += BOOKMARK_HEIGHT;
}
self.window.set_ime_cursor_area(
LogicalPosition::new(position.min.x, position.min.y + height as i32),
LogicalSize::new(0, position.max.y - position.min.y),
);
}
pub fn hide_ime(&self) {
self.window.set_ime_allowed(false);
}
pub fn show_notification(&self, notification: &Notification) {
let mut display_notification = notify_rust::Notification::new();
display_notification
.summary(¬ification.title)
.body(¬ification.body);
#[cfg(linux)]
{
if let Some(icon_image) = notification.icon_resource.as_ref().and_then(|icon| {
Image::from_rgba(icon.width as i32, icon.height as i32, icon.bytes().to_vec()).ok()
}) {
display_notification.image_data(icon_image);
}
}
#[cfg(linux)]
std::thread::spawn(move || {
if let Ok(handle) = display_notification.show() {
handle.on_close(|| {});
}
});
#[cfg(not(linux))]
let _ = display_notification.show();
}
pub(crate) fn close_webview_menu(&mut self, sender: &Sender<EmbedderToConstellationMessage>) {
if let Some(menu) = self.webview_menu.as_mut() {
menu.close(sender);
}
}
}
impl Window {
pub(crate) fn close_prompt_dialog(&mut self, tab_id: WebViewId) {
if let Some(sender) = self
.tab_manager
.remove_prompt_by_tab_id(tab_id)
.and_then(|prompt| prompt.sender())
{
match sender {
PromptSender::AlertSender(sender) => {
let _ = sender.send(AlertResponse::default());
}
PromptSender::ConfirmSender(sender) => {
let _ = sender.send(ConfirmResponse::default());
}
PromptSender::InputSender(sender) => {
let _ = sender.send(PromptResponse::default());
}
PromptSender::AllowDenySender(sender) => {
let _ = sender.send(AllowOrDeny::Deny);
}
PromptSender::HttpBasicAuthSender(sender) => {
let _ = sender.send(None);
}
}
}
}
}
#[cfg(any(linux, target_os = "windows"))]
impl Window {
fn should_use_client_region_drag(&self) -> bool {
!self.window.is_decorated()
&& !self.window.is_maximized()
&& self.window.is_resizable()
&& self.window.fullscreen().is_none()
}
fn drag_resize_window(&self) {
if let Some(direction) = self.get_drag_resize_direction() {
if let Err(err) = self.window.drag_resize_window(direction) {
log::error!("Failed to drag-resize window: {:?}", err);
}
}
}
fn get_drag_resize_direction(&self) -> Option<ResizeDirection> {
let mouse_position = match self.mouse_position.get() {
Some(position) => position,
None => {
return None;
}
};
let window_size = self.window.outer_size();
let border_size = 5.0 * self.window.scale_factor();
let x_direction = if mouse_position.x < border_size {
Some(ResizeDirection::West)
} else if mouse_position.x > (window_size.width as f64 - border_size) {
Some(ResizeDirection::East)
} else {
None
};
let y_direction = if mouse_position.y < border_size {
Some(ResizeDirection::North)
} else if mouse_position.y > (window_size.height as f64 - border_size) {
Some(ResizeDirection::South)
} else {
None
};
let direction = match (x_direction, y_direction) {
(Some(ResizeDirection::East), None) => ResizeDirection::East,
(Some(ResizeDirection::West), None) => ResizeDirection::West,
(None, Some(ResizeDirection::South)) => ResizeDirection::South,
(None, Some(ResizeDirection::North)) => ResizeDirection::North,
(Some(ResizeDirection::East), Some(ResizeDirection::North)) => {
ResizeDirection::NorthEast
}
(Some(ResizeDirection::West), Some(ResizeDirection::North)) => {
ResizeDirection::NorthWest
}
(Some(ResizeDirection::East), Some(ResizeDirection::South)) => {
ResizeDirection::SouthEast
}
(Some(ResizeDirection::West), Some(ResizeDirection::South)) => {
ResizeDirection::SouthWest
}
_ => return None,
};
Some(direction)
}
fn set_drag_resize_cursor(&mut self, direction: Option<ResizeDirection>) {
if let Some(direction) = direction {
let cursor = match direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::West => CursorIcon::WResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
};
self.cursor_state.cursor_resizing = true;
self.window.set_cursor(cursor);
} else if self.cursor_state.cursor_resizing {
self.cursor_state.cursor_resizing = false;
self.window.set_cursor(self.cursor_state.current_cursor);
}
}
}
#[cfg(macos)]
use objc2::runtime::AnyObject;
#[cfg(macos)]
use raw_window_handle::{AppKitWindowHandle, RawWindowHandle};
#[cfg(macos)]
pub unsafe fn decorate_window(view: *mut AnyObject, _position: LogicalPosition<f64>) {
use objc2::rc::Id;
use objc2_app_kit::{NSView, NSWindowStyleMask, NSWindowTitleVisibility};
let ns_view: Id<NSView> = unsafe { Id::retain(view.cast()) }.unwrap();
let window = ns_view
.window()
.expect("view was not installed in a window");
window.setMovable(false); window.setTitlebarAppearsTransparent(true);
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
window.setStyleMask(
NSWindowStyleMask::Titled
| NSWindowStyleMask::FullSizeContentView
| NSWindowStyleMask::Closable
| NSWindowStyleMask::Resizable
| NSWindowStyleMask::Miniaturizable,
);
}
fn forward_input_event(
compositor: &mut IOCompositor,
webview_id: WebViewId,
constellation_proxy: &Sender<EmbedderToConstellationMessage>,
event: InputEvent,
) {
if event.point().is_some() {
compositor.on_input_event(webview_id, event);
return;
}
let _ = constellation_proxy.send(EmbedderToConstellationMessage::ForwardInputEvent(
webview_id, event, None, ));
}