For EventLoop, EventLoopBuilder, EventLoopProxy and by requiring it as a supertrait of Window and ActiveEventLoop. It is especially useful for user to be able to know that Window is Debug.
504 lines
15 KiB
Rust
504 lines
15 KiB
Rust
use std::cell::Ref;
|
|
use std::fmt;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
|
|
use dpi::{LogicalPosition, LogicalSize};
|
|
use web_sys::HtmlCanvasElement;
|
|
|
|
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
|
use super::monitor::MonitorHandler;
|
|
use super::r#async::Dispatcher;
|
|
use super::{backend, lock, ActiveEventLoop};
|
|
use crate::dpi::{LogicalInsets, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
|
|
use crate::error::{NotSupportedError, RequestError};
|
|
use crate::icon::Icon;
|
|
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
|
use crate::window::{
|
|
Cursor, CursorGrabMode, Fullscreen as RootFullscreen, ImePurpose, ResizeDirection, Theme,
|
|
UserAttentionType, Window as RootWindow, WindowAttributes, WindowButtons, WindowId,
|
|
WindowLevel,
|
|
};
|
|
|
|
pub struct Window {
|
|
inner: Dispatcher<Inner>,
|
|
}
|
|
|
|
impl fmt::Debug for Window {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("Window").finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
pub struct Inner {
|
|
id: WindowId,
|
|
pub window: web_sys::Window,
|
|
monitor: Rc<MonitorHandler>,
|
|
safe_area: Rc<backend::SafeAreaHandle>,
|
|
canvas: Rc<backend::Canvas>,
|
|
destroy_fn: Option<Box<dyn FnOnce()>>,
|
|
}
|
|
|
|
impl Window {
|
|
pub(crate) fn new(
|
|
target: &ActiveEventLoop,
|
|
attr: WindowAttributes,
|
|
) -> Result<Self, RequestError> {
|
|
let id = target.generate_id();
|
|
|
|
let window = target.runner.window();
|
|
let navigator = target.runner.navigator();
|
|
let document = target.runner.document();
|
|
let canvas = backend::Canvas::create(
|
|
target.runner.main_thread(),
|
|
id,
|
|
window.clone(),
|
|
navigator.clone(),
|
|
document.clone(),
|
|
attr,
|
|
)?;
|
|
let canvas = Rc::new(canvas);
|
|
|
|
target.register(&canvas, id);
|
|
|
|
let runner = target.runner.clone();
|
|
let destroy_fn = Box::new(move || runner.notify_destroy_window(id));
|
|
|
|
let inner = Inner {
|
|
id,
|
|
window: window.clone(),
|
|
monitor: Rc::clone(target.runner.monitor()),
|
|
safe_area: Rc::clone(target.runner.safe_area()),
|
|
canvas,
|
|
destroy_fn: Some(destroy_fn),
|
|
};
|
|
|
|
let canvas = Rc::downgrade(&inner.canvas);
|
|
let (dispatcher, runner) = Dispatcher::new(target.runner.main_thread(), inner);
|
|
target.runner.add_canvas(id, canvas, runner);
|
|
|
|
Ok(Window { inner: dispatcher })
|
|
}
|
|
|
|
pub fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> {
|
|
MainThreadMarker::new()
|
|
.map(|main_thread| Ref::map(self.inner.value(main_thread), |inner| inner.canvas.raw()))
|
|
}
|
|
|
|
pub(crate) fn prevent_default(&self) -> bool {
|
|
self.inner.queue(|inner| inner.canvas.prevent_default.get())
|
|
}
|
|
|
|
pub(crate) fn set_prevent_default(&self, prevent_default: bool) {
|
|
self.inner.dispatch(move |inner| inner.canvas.prevent_default.set(prevent_default))
|
|
}
|
|
|
|
pub(crate) fn is_cursor_lock_raw(&self) -> bool {
|
|
self.inner.queue(move |inner| {
|
|
lock::is_cursor_lock_raw(inner.canvas.navigator(), inner.canvas.document())
|
|
})
|
|
}
|
|
}
|
|
|
|
impl RootWindow for Window {
|
|
fn id(&self) -> WindowId {
|
|
self.inner.queue(|inner| inner.id)
|
|
}
|
|
|
|
fn scale_factor(&self) -> f64 {
|
|
self.inner.queue(Inner::scale_factor)
|
|
}
|
|
|
|
fn request_redraw(&self) {
|
|
self.inner.dispatch(|inner| inner.canvas.request_animation_frame())
|
|
}
|
|
|
|
fn pre_present_notify(&self) {}
|
|
|
|
fn reset_dead_keys(&self) {
|
|
// Not supported
|
|
}
|
|
|
|
fn surface_position(&self) -> PhysicalPosition<i32> {
|
|
// Note: the canvas element has no window decorations.
|
|
(0, 0).into()
|
|
}
|
|
|
|
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
|
Ok(self.inner.queue(|inner| inner.canvas.position().to_physical(inner.scale_factor())))
|
|
}
|
|
|
|
fn set_outer_position(&self, position: Position) {
|
|
self.inner.dispatch(move |inner| {
|
|
let position = position.to_logical::<f64>(inner.scale_factor());
|
|
backend::set_canvas_position(
|
|
inner.canvas.document(),
|
|
inner.canvas.raw(),
|
|
inner.canvas.style(),
|
|
position,
|
|
)
|
|
})
|
|
}
|
|
|
|
fn surface_size(&self) -> PhysicalSize<u32> {
|
|
self.inner.queue(|inner| inner.canvas.surface_size())
|
|
}
|
|
|
|
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
|
self.inner.queue(|inner| {
|
|
let size = size.to_logical(self.scale_factor());
|
|
backend::set_canvas_size(
|
|
inner.canvas.document(),
|
|
inner.canvas.raw(),
|
|
inner.canvas.style(),
|
|
size,
|
|
);
|
|
None
|
|
})
|
|
}
|
|
|
|
fn outer_size(&self) -> PhysicalSize<u32> {
|
|
// Note: the canvas element has no window decorations, so this is equal to `surface_size`.
|
|
self.surface_size()
|
|
}
|
|
|
|
fn safe_area(&self) -> PhysicalInsets<u32> {
|
|
self.inner.queue(|inner| {
|
|
let (safe_start_pos, safe_size) = inner.safe_area.get();
|
|
let safe_end_pos = LogicalPosition::new(
|
|
safe_start_pos.x + safe_size.width,
|
|
safe_start_pos.y + safe_size.height,
|
|
);
|
|
|
|
let surface_start_pos = inner.canvas.position();
|
|
let surface_size = LogicalSize::new(
|
|
backend::style_size_property(inner.canvas.style(), "width"),
|
|
backend::style_size_property(inner.canvas.style(), "height"),
|
|
);
|
|
let surface_end_pos = LogicalPosition::new(
|
|
surface_start_pos.x + surface_size.width,
|
|
surface_start_pos.y + surface_size.height,
|
|
);
|
|
|
|
let top = f64::max(safe_start_pos.y - surface_start_pos.y, 0.);
|
|
let left = f64::max(safe_start_pos.x - surface_start_pos.x, 0.);
|
|
let bottom = f64::max(surface_end_pos.y - safe_end_pos.y, 0.);
|
|
let right = f64::max(surface_end_pos.x - safe_end_pos.x, 0.);
|
|
|
|
let insets = LogicalInsets::new(top, left, bottom, right);
|
|
insets.to_physical(inner.scale_factor())
|
|
})
|
|
}
|
|
|
|
fn set_min_surface_size(&self, min_size: Option<Size>) {
|
|
self.inner.dispatch(move |inner| {
|
|
let dimensions = min_size.map(|min_size| min_size.to_logical(inner.scale_factor()));
|
|
backend::set_canvas_min_size(
|
|
inner.canvas.document(),
|
|
inner.canvas.raw(),
|
|
inner.canvas.style(),
|
|
dimensions,
|
|
)
|
|
})
|
|
}
|
|
|
|
fn set_max_surface_size(&self, max_size: Option<Size>) {
|
|
self.inner.dispatch(move |inner| {
|
|
let dimensions = max_size.map(|dimensions| dimensions.to_logical(inner.scale_factor()));
|
|
backend::set_canvas_max_size(
|
|
inner.canvas.document(),
|
|
inner.canvas.raw(),
|
|
inner.canvas.style(),
|
|
dimensions,
|
|
)
|
|
})
|
|
}
|
|
|
|
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
|
None
|
|
}
|
|
|
|
fn set_surface_resize_increments(&self, _: Option<Size>) {
|
|
// Intentionally a no-op: users can't resize canvas elements
|
|
}
|
|
|
|
fn set_title(&self, title: &str) {
|
|
self.inner.queue(|inner| inner.canvas.set_attribute("alt", title))
|
|
}
|
|
|
|
fn set_transparent(&self, _: bool) {}
|
|
|
|
fn set_blur(&self, _: bool) {}
|
|
|
|
fn set_visible(&self, _: bool) {
|
|
// Intentionally a no-op
|
|
}
|
|
|
|
fn is_visible(&self) -> Option<bool> {
|
|
None
|
|
}
|
|
|
|
fn set_resizable(&self, _: bool) {
|
|
// Intentionally a no-op: users can't resize canvas elements
|
|
}
|
|
|
|
fn is_resizable(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn set_enabled_buttons(&self, _: WindowButtons) {}
|
|
|
|
fn enabled_buttons(&self) -> WindowButtons {
|
|
WindowButtons::all()
|
|
}
|
|
|
|
fn set_minimized(&self, _: bool) {
|
|
// Intentionally a no-op, as canvases cannot be 'minimized'
|
|
}
|
|
|
|
fn is_minimized(&self) -> Option<bool> {
|
|
// Canvas cannot be 'minimized'
|
|
Some(false)
|
|
}
|
|
|
|
fn set_maximized(&self, _: bool) {
|
|
// Intentionally a no-op, as canvases cannot be 'maximized'
|
|
}
|
|
|
|
fn is_maximized(&self) -> bool {
|
|
// Canvas cannot be 'maximized'
|
|
false
|
|
}
|
|
|
|
fn set_fullscreen(&self, fullscreen: Option<RootFullscreen>) {
|
|
self.inner.dispatch(move |inner| {
|
|
if let Some(fullscreen) = fullscreen {
|
|
inner.canvas.request_fullscreen(fullscreen.into());
|
|
} else {
|
|
inner.canvas.exit_fullscreen()
|
|
}
|
|
})
|
|
}
|
|
|
|
fn fullscreen(&self) -> Option<RootFullscreen> {
|
|
self.inner.queue(|inner| {
|
|
if inner.canvas.is_fullscreen() {
|
|
Some(RootFullscreen::Borderless(None))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
fn set_decorations(&self, _: bool) {
|
|
// Intentionally a no-op, no canvas decorations
|
|
}
|
|
|
|
fn is_decorated(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn set_window_level(&self, _: WindowLevel) {
|
|
// Intentionally a no-op, no window ordering
|
|
}
|
|
|
|
fn set_window_icon(&self, _: Option<Icon>) {
|
|
// Currently an intentional no-op
|
|
}
|
|
|
|
fn set_ime_cursor_area(&self, _: Position, _: Size) {
|
|
// Currently not implemented
|
|
}
|
|
|
|
fn set_ime_allowed(&self, _: bool) {
|
|
// Currently not implemented
|
|
}
|
|
|
|
fn set_ime_purpose(&self, _: ImePurpose) {
|
|
// Currently not implemented
|
|
}
|
|
|
|
fn focus_window(&self) {
|
|
self.inner.dispatch(|inner| {
|
|
let _ = inner.canvas.raw().focus();
|
|
})
|
|
}
|
|
|
|
fn has_focus(&self) -> bool {
|
|
self.inner.queue(|inner| inner.canvas.has_focus.get())
|
|
}
|
|
|
|
fn request_user_attention(&self, _: Option<UserAttentionType>) {
|
|
// Currently an intentional no-op
|
|
}
|
|
|
|
fn set_theme(&self, _: Option<Theme>) {}
|
|
|
|
fn theme(&self) -> Option<Theme> {
|
|
self.inner.queue(|inner| {
|
|
backend::is_dark_mode(&inner.window).map(|is_dark_mode| {
|
|
if is_dark_mode {
|
|
Theme::Dark
|
|
} else {
|
|
Theme::Light
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
fn set_content_protected(&self, _: bool) {}
|
|
|
|
fn title(&self) -> String {
|
|
String::new()
|
|
}
|
|
|
|
fn set_cursor(&self, cursor: Cursor) {
|
|
self.inner.dispatch(move |inner| inner.canvas.cursor.set_cursor(cursor))
|
|
}
|
|
|
|
fn set_cursor_position(&self, _: Position) -> Result<(), RequestError> {
|
|
Err(NotSupportedError::new("set_cursor_position is not supported").into())
|
|
}
|
|
|
|
fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
|
|
Ok(self.inner.queue(|inner| {
|
|
match mode {
|
|
CursorGrabMode::None => inner.canvas.document().exit_pointer_lock(),
|
|
CursorGrabMode::Locked => lock::request_pointer_lock(
|
|
inner.canvas.navigator(),
|
|
inner.canvas.document(),
|
|
inner.canvas.raw(),
|
|
),
|
|
CursorGrabMode::Confined => {
|
|
return Err(NotSupportedError::new("confined cursor mode is not supported"))
|
|
},
|
|
}
|
|
|
|
Ok(())
|
|
})?)
|
|
}
|
|
|
|
fn set_cursor_visible(&self, visible: bool) {
|
|
self.inner.dispatch(move |inner| inner.canvas.cursor.set_cursor_visible(visible))
|
|
}
|
|
|
|
fn drag_window(&self) -> Result<(), RequestError> {
|
|
Err(NotSupportedError::new("drag_window is not supported").into())
|
|
}
|
|
|
|
fn drag_resize_window(&self, _: ResizeDirection) -> Result<(), RequestError> {
|
|
Err(NotSupportedError::new("drag_resize_window is not supported").into())
|
|
}
|
|
|
|
fn show_window_menu(&self, _: Position) {}
|
|
|
|
fn set_cursor_hittest(&self, _: bool) -> Result<(), RequestError> {
|
|
Err(NotSupportedError::new("set_cursor_hittest is not supported").into())
|
|
}
|
|
|
|
fn current_monitor(&self) -> Option<RootMonitorHandle> {
|
|
Some(self.inner.queue(|inner| inner.monitor.current_monitor()).into())
|
|
}
|
|
|
|
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
|
|
Box::new(
|
|
self.inner
|
|
.queue(|inner| inner.monitor.available_monitors())
|
|
.into_iter()
|
|
.map(RootMonitorHandle::from),
|
|
)
|
|
}
|
|
|
|
fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
self.inner.queue(|inner| inner.monitor.primary_monitor()).map(RootMonitorHandle::from)
|
|
}
|
|
|
|
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
|
self
|
|
}
|
|
|
|
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl rwh_06::HasWindowHandle for Window {
|
|
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
|
MainThreadMarker::new()
|
|
.map(|main_thread| {
|
|
let inner = self.inner.value(main_thread);
|
|
// SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid.
|
|
let canvas: &wasm_bindgen::JsValue = inner.canvas.raw();
|
|
let window_handle =
|
|
rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast());
|
|
// SAFETY: The pointer won't be invalidated as long as `Window` lives, which the
|
|
// lifetime is bound to.
|
|
unsafe {
|
|
rwh_06::WindowHandle::borrow_raw(rwh_06::RawWindowHandle::WebCanvas(
|
|
window_handle,
|
|
))
|
|
}
|
|
})
|
|
.ok_or(rwh_06::HandleError::Unavailable)
|
|
}
|
|
}
|
|
|
|
impl rwh_06::HasDisplayHandle for Window {
|
|
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
|
Ok(rwh_06::DisplayHandle::web())
|
|
}
|
|
}
|
|
|
|
impl Inner {
|
|
#[inline]
|
|
pub fn scale_factor(&self) -> f64 {
|
|
super::backend::scale_factor(&self.window)
|
|
}
|
|
}
|
|
|
|
impl Drop for Inner {
|
|
fn drop(&mut self) {
|
|
if let Some(destroy_fn) = self.destroy_fn.take() {
|
|
destroy_fn();
|
|
}
|
|
}
|
|
}
|
|
#[derive(Clone, Debug)]
|
|
pub struct PlatformSpecificWindowAttributes {
|
|
pub(crate) canvas: Option<Arc<MainThreadSafe<backend::RawCanvasType>>>,
|
|
pub(crate) prevent_default: bool,
|
|
pub(crate) focusable: bool,
|
|
pub(crate) append: bool,
|
|
}
|
|
|
|
impl PartialEq for PlatformSpecificWindowAttributes {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
(match (&self.canvas, &other.canvas) {
|
|
(Some(this), Some(other)) => Arc::ptr_eq(this, other),
|
|
(None, None) => true,
|
|
_ => false,
|
|
}) && self.prevent_default.eq(&other.prevent_default)
|
|
&& self.focusable.eq(&other.focusable)
|
|
&& self.append.eq(&other.append)
|
|
}
|
|
}
|
|
|
|
impl PlatformSpecificWindowAttributes {
|
|
pub(crate) fn set_canvas(&mut self, canvas: Option<backend::RawCanvasType>) {
|
|
let Some(canvas) = canvas else {
|
|
self.canvas = None;
|
|
return;
|
|
};
|
|
|
|
let main_thread = MainThreadMarker::new()
|
|
.expect("received a `HtmlCanvasElement` outside the window context");
|
|
|
|
self.canvas = Some(Arc::new(MainThreadSafe::new(main_thread, canvas)));
|
|
}
|
|
}
|
|
|
|
impl Default for PlatformSpecificWindowAttributes {
|
|
fn default() -> Self {
|
|
Self { canvas: None, prevent_default: true, focusable: true, append: false }
|
|
}
|
|
}
|