diff --git a/.circleci/config.yml b/.circleci/config.yml index b247266cf5..624f1e3164 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ jobs: paths: - target - wasm-test: + emscripten-test: working_directory: ~/winit docker: - image: tomaka/rustc-emscripten @@ -43,7 +43,22 @@ jobs: key: wasm-test-cache-{{ checksum "Cargo.toml" }} - run: cargo build --example window --target wasm32-unknown-emscripten - save_cache: + key: emscripten-test-cache-{{ checksum "Cargo.toml" }} + paths: + - target + + websys-test: + working_directory: ~/winit + docker: + - image: tomaka/rustc-emscripten + steps: + - run: apt-get -qq update && apt-get install -y git + - checkout + - restore_cache: key: wasm-test-cache-{{ checksum "Cargo.toml" }} + - run: cargo build --example wasm_bindgen --target wasm32-unknown-unknown --features websys + - save_cache: + key: websys-test-cache-{{ checksum "Cargo.toml" }} paths: - target @@ -53,4 +68,5 @@ workflows: jobs: - android-test - asmjs-test - - wasm-test + - emscripten-test + - websys-test diff --git a/Cargo.toml b/Cargo.toml index 0599ec6927..bd47c33505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ categories = ["gui"] [package.metadata.docs.rs] features = ["serde"] +[features] +websys = ["wasm-bindgen", "web-sys"] + [dependencies] lazy_static = "1" libc = "0.2" @@ -72,3 +75,24 @@ percent-encoding = "1.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] version = "0.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] +version = "0.2" +optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3.4" +optional = true +features = [ + 'Document', + 'Element', + 'HtmlElement', + 'HtmlCanvasElement', + 'CanvasRenderingContext2d', + 'MouseEvent', + 'Node', + 'Window', + 'console', + 'CssStyleDeclaration', + 'DomRect', +] diff --git a/examples/wasm_bindgen.rs b/examples/wasm_bindgen.rs new file mode 100644 index 0000000000..79afa982a7 --- /dev/null +++ b/examples/wasm_bindgen.rs @@ -0,0 +1,74 @@ +extern crate web_sys; +extern crate wasm_bindgen; + +extern crate winit; +use winit::window::WindowBuilder; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::{EventLoop, ControlFlow}; +use winit::platform::websys::WebsysWindowExt; +use winit::platform::websys::WebsysWindowBuilderExt; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// A macro to provide `println!(..)`-style syntax for `console.log` logging. +macro_rules! log { + ( $( $t:tt )* ) => { + web_sys::console::log_1(&format!( $( $t )* ).into()); + } +} + +// use wee_alloc if it is available +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +#[wasm_bindgen] +struct App {} + +#[wasm_bindgen] +impl App { + pub fn new() -> App { + App{} + } + + pub fn run(&self) { + // create an event loop + let event_loop = EventLoop::new(); + + // create a window and associate it with the event loop + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_canvas_id("test") + .build(&event_loop) + .unwrap(); + + // do some drawing + let canvas = window.get_canvas(); + let ctx = canvas.get_context("2d").unwrap().unwrap() + .dyn_into::().unwrap(); + ctx.begin_path(); + ctx.arc(95.0, 50.0, 40.0, 0.0, 2.0 * 3.14159).unwrap(); + ctx.stroke(); + + // run forever + // + // when using wasm_bindgen, this will currently throw a js + // exception once all browser event handlers have been installed. + event_loop.run(|event, _, control_flow| { + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => *control_flow = ControlFlow::Poll, + } + }); + } +} + +pub fn main() { + let app = App::new(); + app.run(); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 347f8e1cf2..490cc4736b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,10 @@ extern crate percent_encoding; extern crate smithay_client_toolkit as sctk; #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] extern crate calloop; +#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "websys"))] +extern crate wasm_bindgen; +#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "websys"))] +extern crate web_sys; pub mod dpi; #[macro_use] diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ba494ac6d3..59cecde286 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -19,5 +19,6 @@ pub mod ios; pub mod macos; pub mod unix; pub mod windows; +pub mod websys; pub mod desktop; diff --git a/src/platform/websys.rs b/src/platform/websys.rs new file mode 100644 index 0000000000..b2c598f428 --- /dev/null +++ b/src/platform/websys.rs @@ -0,0 +1,33 @@ +extern crate wasm_bindgen; +extern crate web_sys; + +use platform_impl::window::ElementSelection; +use window::{Window, WindowBuilder}; + +pub trait WebsysWindowExt { + fn get_canvas<'a>(&'a self) -> &'a web_sys::HtmlCanvasElement; +} + +impl WebsysWindowExt for Window { + fn get_canvas<'a>(&'a self) -> &'a web_sys::HtmlCanvasElement { + &self.window.canvas + } +} + +pub trait WebsysWindowBuilderExt { + fn with_canvas_id(self, canvas_id: &str) -> WindowBuilder; + + fn with_container_id(self, container_id: &str) -> WindowBuilder; +} + +impl WebsysWindowBuilderExt for WindowBuilder { + fn with_canvas_id(mut self, canvas_id: &str) -> WindowBuilder { + self.platform_specific.element = ElementSelection::CanvasId(canvas_id.to_string()); + self + } + + fn with_container_id(mut self, container_id: &str) -> WindowBuilder { + self.platform_specific.element = ElementSelection::ContainerId(container_id.to_string()); + self + } +} diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index be96878500..cca65b2578 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -18,9 +18,12 @@ mod platform; #[cfg(target_os = "emscripten")] #[path="emscripten/mod.rs"] mod platform; +#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "websys"))] +#[path="websys/mod.rs"] +mod platform; #[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"), not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"), not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"), - not(target_os = "emscripten")))] + not(target_os = "emscripten"), not(all(target_arch = "wasm32", feature="websys"))))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/websys/event.rs b/src/platform_impl/websys/event.rs new file mode 100644 index 0000000000..0e428dbe5a --- /dev/null +++ b/src/platform_impl/websys/event.rs @@ -0,0 +1,19 @@ +use std::convert::From; + +use ::event::WindowEvent as WindowEvent; +use ::event::DeviceId as WDeviceId; +use ::event::{ElementState, MouseButton}; + +use ::web_sys::MouseEvent; +use super::window::DeviceId; + +impl From for WindowEvent { + fn from(event: MouseEvent) -> Self { + WindowEvent::MouseInput { + device_id: WDeviceId(DeviceId::dummy()), + state: ElementState::Pressed, + button: MouseButton::Left, + modifiers: Default::default() + } + } +} \ No newline at end of file diff --git a/src/platform_impl/websys/event_loop.rs b/src/platform_impl/websys/event_loop.rs new file mode 100644 index 0000000000..b00aed00a8 --- /dev/null +++ b/src/platform_impl/websys/event_loop.rs @@ -0,0 +1,181 @@ +use ::event_loop::{ControlFlow, EventLoopClosed}; +use ::event_loop::EventLoopWindowTarget as WinitELT; +use ::event::{Event, StartCause}; +use super::window::{MonitorHandle, WindowId}; +#[macro_use] +use platform_impl::platform::wasm_util as util; + +use std::collections::VecDeque; +use std::rc::Rc; +use std::cell::{Cell, RefCell}; + +use ::wasm_bindgen::prelude::*; +use ::wasm_bindgen::JsCast; +use ::web_sys::{HtmlCanvasElement}; + +#[derive(Clone, Copy, Eq, PartialEq)] +enum EventLoopState { + Sleeping, + Waking, + Polling +} + +impl Default for EventLoopState { + #[inline(always)] + fn default() -> Self { + EventLoopState::Polling + } +} + +pub struct EventLoopWindowTarget { + pub(crate) window_events: Rc>>, + pub(crate) internal: Rc, + _marker: std::marker::PhantomData +} + +impl EventLoopWindowTarget { + pub fn events(&self) -> Vec<::event::WindowEvent> { + self.window_events.replace(Vec::new()) + } + + + pub fn setup_window(&self, element: &HtmlCanvasElement) { + let events = self.window_events.clone(); + let internal = self.internal.clone(); + let handler = Closure::wrap(Box::new(move |event: ::web_sys::MouseEvent| { + events.borrow_mut().push(event.into()); + if internal.is_sleeping() { + internal.wake(); + } + }) as Box); + element.set_onmousedown(Some(handler.as_ref().unchecked_ref())); + handler.forget(); + } +} + +pub struct EventLoop { + window_target: WinitELT, +} + +pub(crate) struct EventLoopInternal { + loop_fn: Rc>>>, + state: Cell, +} + +impl EventLoopInternal { + pub(crate) fn sleep(&self) { + self.state.set(EventLoopState::Sleeping); + } + + pub(crate) fn wake(&self) { + self.state.set(EventLoopState::Waking); + let window = web_sys::window().expect("should be a window"); + // TODO: call this directly? + window.request_animation_frame(self.loop_fn.borrow().as_ref().unwrap().as_ref().unchecked_ref()); + } + + pub(crate) fn is_sleeping(&self) -> bool { + self.state.get() == EventLoopState::Sleeping + } +} + +impl EventLoop { + pub fn new() -> EventLoop { + let loop_fn: Rc>>> = Rc::new(RefCell::new(None)); + EventLoop { + window_target: WinitELT { + p: EventLoopWindowTarget { + window_events: Rc::new(RefCell::new(Vec::new())), + internal: Rc::new(EventLoopInternal { + state: Cell::default(), + loop_fn, + }), + _marker: std::marker::PhantomData + }, + _marker: std::marker::PhantomData + }, + } + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { _marker: std::marker::PhantomData } + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + vec!(MonitorHandle{}).into_iter().collect() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle{} + } + + #[inline] + pub fn run(self, event_handler: F) -> ! + where F: 'static + FnMut(Event, &WinitELT, &mut ControlFlow) + { + self.run_return(event_handler); + log!("exiting"); + + util::js_exit(); + unreachable!() + } + + fn run_return(self, mut event_handler: F) + where F: 'static + FnMut(Event, &WinitELT, &mut ControlFlow) + { + let mut control_flow = ControlFlow::default(); + + let g = self.window_target.p.internal.loop_fn.clone(); + + event_handler(Event::NewEvents(StartCause::Init), &self.window_target, &mut control_flow); + + *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { + match control_flow { + ControlFlow::Poll => { + log!("Starting poll!!!"); + let mut win_events = self.window_target.p.events(); + win_events.drain(..).for_each(|e| { + event_handler(Event::WindowEvent{window_id: ::window::WindowId(WindowId{}), event: e}, &self.window_target, &mut control_flow); + }); + + event_handler(Event::NewEvents(StartCause::Poll), &self.window_target, &mut control_flow); + + let window = web_sys::window().expect("should be a window"); + window.request_animation_frame(self.window_target.p.internal.loop_fn.borrow().as_ref().unwrap().as_ref().unchecked_ref()); + }, + ControlFlow::Wait => { + let mut win_events = self.window_target.p.events(); + win_events.drain(..).for_each(|e| { + event_handler(Event::WindowEvent{window_id: ::window::WindowId(WindowId{}), event: e}, &self.window_target, &mut control_flow); + }); + self.window_target.p.internal.sleep(); + event_handler(Event::Suspended(true), &self.window_target, &mut control_flow); + }, + _ => { + unreachable!(); + } + } + }) as Box)); + let window = web_sys::window().expect("should be a window"); + window.request_animation_frame(g.borrow().as_ref().unwrap().as_ref().unchecked_ref()); + } + + + pub fn window_target(&self) -> &WinitELT { + &self.window_target + } + +} + +#[derive(Clone)] +pub struct EventLoopProxy { + _marker: std::marker::PhantomData +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + unimplemented!() + } +} diff --git a/src/platform_impl/websys/mod.rs b/src/platform_impl/websys/mod.rs new file mode 100644 index 0000000000..7e58fbb77e --- /dev/null +++ b/src/platform_impl/websys/mod.rs @@ -0,0 +1,19 @@ +pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +pub use self::window::{DeviceId, MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; + +use std::fmt::{Display, Error, Formatter}; + +#[macro_use] +mod wasm_util; +mod event_loop; +mod event; +pub mod window; + +#[derive(Debug)] +pub struct OsError; + +impl Display for OsError { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { + formatter.pad(&format!("websys error")) + } +} \ No newline at end of file diff --git a/src/platform_impl/websys/wasm_util.rs b/src/platform_impl/websys/wasm_util.rs new file mode 100644 index 0000000000..d7a18aa86f --- /dev/null +++ b/src/platform_impl/websys/wasm_util.rs @@ -0,0 +1,22 @@ +use wasm_bindgen::prelude::*; +use ::error::OsError as WOsError; + +use super::OsError; + +// A macro to provide `println!(..)`-style syntax for `console.log` logging. +macro_rules! log { + ( $( $t:tt )* ) => { + web_sys::console::log_1(&format!( $( $t )* ).into()); + } +} + +#[wasm_bindgen(inline_js = "export function js_exit() { throw 'hacky exit!'; }")] +extern "C" { + pub fn js_exit(); +} + +impl From for WOsError { + fn from(error: wasm_bindgen::JsValue) -> Self { + os_error!(OsError{}) + } +} diff --git a/src/platform_impl/websys/window.rs b/src/platform_impl/websys/window.rs new file mode 100644 index 0000000000..8655bf44ee --- /dev/null +++ b/src/platform_impl/websys/window.rs @@ -0,0 +1,592 @@ +use window::{WindowAttributes}; +use std::collections::VecDeque; +use std::rc::Rc; +use std::cell::Cell; +use dpi::{PhysicalPosition, LogicalPosition, PhysicalSize, LogicalSize}; +use icon::Icon; +use super::event_loop::{EventLoopWindowTarget}; + +use ::error::{ExternalError, NotSupportedError}; +use ::window::CursorIcon; + +use ::wasm_bindgen::JsCast; +use web_sys::HtmlElement; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(u32); + +impl DeviceId { + pub fn dummy() -> Self { + DeviceId(0) + } +} + +/// +/// ElementSelection allows the window creator +/// to select an existing canvas in the DOM +/// or a container in which to create a canvas. +/// +#[derive(Clone)] +pub enum ElementSelection { + CanvasId(String), + ContainerId(String) +} + +impl Default for ElementSelection { + fn default() -> Self { ElementSelection::CanvasId("".to_string()) } +} + +/// +/// Platform specific attributes for window creation. +/// +#[derive(Clone, Default)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub element: ElementSelection +} + +#[derive(Copy, Clone, Debug)] +pub struct MonitorHandle; + +impl MonitorHandle { + /// Returns a human-readable name of the monitor. + /// + /// Returns `None` if the monitor doesn't exist anymore. + #[inline] + pub fn name(&self) -> Option { + Some(String::from("Browser Window")) + } + + /// Returns the monitor's resolution. + #[inline] + pub fn dimensions(&self) -> PhysicalSize { + let win = ::web_sys::window().expect("there to be a window"); + let w = match win.inner_width() { + Ok(val) => val.as_f64().unwrap(), + Err(val) => 0.0 + }; + let h = match win.inner_height() { + Ok(val) => val.as_f64().unwrap(), + Err(val) => 0.0 + }; + + (w, h).into() + } + + /// Returns the top-left corner position of the monitor relative to the larger full + /// screen area. + #[inline] + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() + } + + /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](dpi/index.html) module for more information. + /// + /// ## Platform-specific + /// + /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **Android:** Always returns 1.0. + #[inline] + pub fn hidpi_factor(&self) -> f64 { + 1.0 + } +} + +pub struct Window { + pub(crate) canvas: ::web_sys::HtmlCanvasElement, + pub(crate) redraw_requested: Cell +} + +pub(crate) struct WindowInternal<'a, T: 'static> { + pub target: &'a EventLoopWindowTarget, + _marker: std::marker::PhantomData, +} + +impl Window { + /// Creates a new Window for platforms where this is appropriate. + /// + /// This function is equivalent to `WindowBuilder::new().build(event_loop)`. + /// + /// Error should be very rare and only occur in case of permission denied, incompatible system, + /// out of memory, etc. + #[inline] + pub fn new(target: &EventLoopWindowTarget, + attr: WindowAttributes, + ps_attr: PlatformSpecificWindowBuilderAttributes) + -> Result { + let window = ::web_sys::window() + .expect("No global window object found!"); + let document = window.document() + .expect("Global window does not have a document!"); + + let element = match ps_attr.element { + ElementSelection::CanvasId(id) => { + document.get_element_by_id(&id) + .expect(&format!("No canvas with ID {} found", id)) + .dyn_into::<::web_sys::HtmlCanvasElement>().unwrap() + }, + ElementSelection::ContainerId(id) => { + let parent = document.get_element_by_id(&id) + .expect(&format!("No container element with Id {} found", id)); + + let canvas = document.create_element("canvas") + .expect("Could not create a canvas") + .dyn_into::<::web_sys::HtmlCanvasElement>().unwrap(); + + parent.append_child(&canvas)?; + + canvas + } + }; + + target.setup_window(&element); + + Ok(Window { + canvas: element, + redraw_requested: Cell::new(false) + }) + } + + /// Returns an identifier unique to the window. + #[inline] + pub fn id(&self) -> WindowId { + WindowId::dummy() + } + + /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](dpi/index.html) module for more information. + /// + /// Note that this value can change depending on user action (for example if the window is + /// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is + /// the most robust way to track the DPI you need to use to draw. + /// + /// ## Platform-specific + /// + /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **Android:** Always returns 1.0. + /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s + /// [`contentScaleFactor`]. + /// + /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc + #[inline] + pub fn hidpi_factor(&self) -> f64 { + 1.0 + } + + /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS + /// events have been processed by the event loop. + /// + /// This is the **strongly encouraged** method of redrawing windows, as it can integrates with + /// OS-requested redraws (e.g. when a window gets resized). + /// + /// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared` + /// but before `Event::NewEvents` if called in the following circumstances: + /// * While processing `EventsCleared`. + /// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any + /// directly subsequent `RedrawRequested` event. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn request_redraw(&self) { + self.redraw_requested.replace(true); + } + + #[inline] + fn canvas_as_element(&self) -> &HtmlElement { + &self.canvas + } +} + +/// Position and size functions. +impl Window { + /// Returns the position of the top-left hand corner of the window's client area relative to the + /// top-left hand corner of the desktop. + /// + /// The same conditions that apply to `outer_position` apply to this method. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window's [safe area] in the screen space coordinate system. + /// + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[inline] + pub fn inner_position(&self) -> Result { + let rect = self.canvas_as_element().get_bounding_client_rect(); + Ok((rect.x(), rect.y()).into()) + } + + /// Returns the position of the top-left hand corner of the window relative to the + /// top-left hand corner of the desktop. + /// + /// Note that the top-left hand corner of the desktop is not necessarily the same as + /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner + /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. + /// + /// The coordinates can be negative if the top-left hand corner of the window is outside + /// of the visible screen region. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window in the screen space coordinate system. + #[inline] + pub fn outer_position(&self) -> Result { + self.inner_position() + } + + /// Modifies the position of the window. + /// + /// See `outer_position` for more information about the coordinates. + /// + /// This is a no-op if the window has already been closed. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the + /// window in the screen space coordinate system. + #[inline] + pub fn set_outer_position(&self, position: LogicalPosition) { + // TODO: support this? + unimplemented!() + } + + /// Returns the logical size of the window's client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. + /// + /// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window's + /// [safe area] in screen space coordinates. + /// + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[inline] + pub fn inner_size(&self) -> LogicalSize { + let rect = self.canvas_as_element().get_bounding_client_rect(); + (rect.width(), rect.height()).into() + } + + /// Modifies the inner size of the window. + /// + /// See `inner_size` for more information about the values. + /// + /// ## Platform-specific + /// + /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` + /// would mean for iOS. + #[inline] + pub fn set_inner_size(&self, size: LogicalSize) { + unimplemented!() + } + + /// Returns the logical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), + /// use `inner_size` instead. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window in + /// screen space coordinates. + #[inline] + pub fn outer_size(&self) -> LogicalSize { + self.inner_size() + } + + /// Sets a minimum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_min_inner_size(&self, dimensions: Option) { + unimplemented!() + } + + /// Sets a maximum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_max_inner_size(&self, dimensions: Option) { + unimplemented!() + } +} + +/// Misc. attribute functions. +impl Window { + /// Modifies the title of the window. + /// + /// ## Platform-specific + /// + /// - Has no effect on iOS. + #[inline] + pub fn set_title(&self, title: &str) { + unimplemented!() + } + + /// Modifies the window's visibility. + /// + /// If `false`, this will hide the window. If `true`, this will show the window. + /// ## Platform-specific + /// + /// - **Android:** Has no effect. + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn set_visible(&self, visible: bool) { + unimplemented!() + } + + /// Sets whether the window is resizable or not. + /// + /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be + /// triggered by DPI scaling, entering fullscreen mode, etc. + /// + /// ## Platform-specific + /// + /// This only has an effect on desktop platforms. + /// + /// Due to a bug in XFCE, this has no effect on Xfwm. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_resizable(&self, resizable: bool) { + unimplemented!() + } + + /// Sets the window to maximized or back. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_maximized(&self, maximized: bool) { + // no-op + } + + /// Sets the window to fullscreen or back. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn set_fullscreen(&self, monitor: Option<::monitor::MonitorHandle>) { + // no-op + } + + /// Gets the window's current fullscreen state. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn fullscreen(&self) -> Option<::monitor::MonitorHandle> { + None + } + + /// Turn window decorations on or off. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden + /// via [`setPrefersStatusBarHidden`]. + /// + /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc + #[inline] + pub fn set_decorations(&self, decorations: bool) { + // no-op + } + + /// Change whether or not the window will always be on top of other windows. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + // no-op + } + + /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left + /// corner of the titlebar. + /// + /// For more usage notes, see `WindowBuilder::with_window_icon`. + /// + /// ## Platform-specific + /// + /// This only has an effect on Windows and X11. + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + // TODO: set favicon? + unimplemented!() + } + + /// Sets location of IME candidate box in client area coordinates relative to the top left. + /// + /// ## Platform-specific + /// + /// **iOS:** Has no effect. + #[inline] + pub fn set_ime_position(&self, position: LogicalPosition) { + // no-op + } +} + +/// Cursor functions. +impl Window { + /// Modifies the cursor icon of the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + /// - **Android:** Has no effect. + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let cursor_style = match cursor { + CursorIcon::Crosshair => "crosshair", + CursorIcon::Hand => "pointer", + CursorIcon::Move => "move", + CursorIcon::Text => "text", + CursorIcon::Wait => "wait", + CursorIcon::Help => "help", + CursorIcon::Progress => "progress", + + /// Cursor showing that something cannot be done. + CursorIcon::NotAllowed => "not-allowed", + CursorIcon::ContextMenu => "context-menu", + CursorIcon::Cell => "cell", + CursorIcon::VerticalText => "vertical-text", + CursorIcon::Alias => "alias", + CursorIcon::Copy => "copy", + CursorIcon::NoDrop => "no-drop", + + CursorIcon::EResize => "e-resize", + CursorIcon::NResize => "n-resize", + CursorIcon::NeResize => "ne-resize", + CursorIcon::NwResize => "nw-resize", + CursorIcon::SResize => "s-resize", + CursorIcon::SeResize => "se-resize", + CursorIcon::SwResize => "sw-resize", + CursorIcon::WResize => "w-resize", + CursorIcon::EwResize => "ew-resize", + CursorIcon::NsResize => "ns-resize", + CursorIcon::NeswResize => "nesw-resize", + CursorIcon::NwseResize => "nwse-resize", + CursorIcon::ColResize => "col-resize", + CursorIcon::RowResize => "row-resize", + _ => "auto", + }; + self.canvas_as_element().style().set_property("cursor", cursor_style).unwrap(); + } + + /// Changes the position of the cursor in window coordinates. + /// + /// ## Platform-specific + /// + /// - **iOS:** Always returns an `Err`. + #[inline] + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { + // unsupported + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// ## Platform-specific + /// + /// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually + /// awkward. + /// - **Android:** Has no effect. + /// - **iOS:** Always returns an Err. + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + // unsupported + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + /// Modifies the cursor's visibility. + /// + /// If `false`, this will hide the cursor. If `true`, this will show the cursor. + /// + /// ## Platform-specific + /// + /// - **Windows:** The cursor is only hidden within the confines of the window. + /// - **X11:** The cursor is only hidden within the confines of the window. + /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is + /// outside of the window. + /// - **iOS:** Has no effect. + /// - **Android:** Has no effect. + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + let style = self.canvas_as_element().style(); + if visible { + style.set_property("cursor", "none").unwrap(); + } else { + style.set_property("cursor", "auto").unwrap(); + } + } +} + +/// Monitor info functions. +impl Window { + /// Returns the monitor on which the window currently resides + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn current_monitor(&self) -> ::monitor::MonitorHandle { + ::monitor::MonitorHandle{ inner: MonitorHandle{} } + } + + /// Returns the list of all the monitors available on the system. + /// + /// This is the same as `EventLoop::available_monitors`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn available_monitors(&self) -> VecDeque { + vec![MonitorHandle{}].into() + } + + /// Returns the primary monitor of the system. + /// + /// This is the same as `EventLoop::primary_monitor`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle { } + } +} + + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId { +} + + +impl WindowId { + /// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `WindowId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub fn dummy() -> Self { + WindowId{} + } +}