Implement hidpi for web platform (#1233)

* fix: use a 'static lifetime for the web backend's `Event` types

* implement hidpi for stdweb (web-sys wip?)

* fix: make all canvas resizes go through backend::set_canvas_size

* update Window docs for web, make `inner/outer_position` return the position in the viewport
This commit is contained in:
Michael Tang 2019-12-31 14:39:33 -08:00 committed by Osspial
parent 28a20aec10
commit 777d9edeaa
11 changed files with 137 additions and 91 deletions

View file

@ -90,6 +90,7 @@ version = "0.3.22"
optional = true
features = [
'console',
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',

View file

@ -37,7 +37,8 @@ impl<T> EventLoop<T> {
pub fn run<F>(self, mut event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
F: 'static
+ FnMut(Event<'static, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
{
let target = root::EventLoopWindowTarget {
p: self.elw.p.clone(),

View file

@ -11,7 +11,7 @@ use std::{
rc::Rc,
};
pub struct Shared<T>(Rc<Execution<T>>);
pub struct Shared<T: 'static>(Rc<Execution<T>>);
impl<T> Clone for Shared<T> {
fn clone(&self) -> Self {
@ -19,21 +19,21 @@ impl<T> Clone for Shared<T> {
}
}
pub struct Execution<T> {
pub struct Execution<T: 'static> {
runner: RefCell<Option<Runner<T>>>,
events: RefCell<VecDeque<Event<T>>>,
events: RefCell<VecDeque<Event<'static, T>>>,
id: RefCell<u32>,
redraw_pending: RefCell<HashSet<WindowId>>,
}
struct Runner<T> {
struct Runner<T: 'static> {
state: State,
is_busy: bool,
event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>,
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
}
impl<T: 'static> Runner<T> {
pub fn new(event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) -> Self {
pub fn new(event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>) -> Self {
Runner {
state: State::Init,
is_busy: false,
@ -55,7 +55,10 @@ impl<T: 'static> Shared<T> {
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootEventLoopWindowTarget reference
pub fn set_listener(&self, event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) {
pub fn set_listener(
&self,
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
) {
self.0.runner.replace(Some(Runner::new(event_handler)));
self.send_event(Event::NewEvents(StartCause::Init));
@ -79,7 +82,7 @@ impl<T: 'static> Shared<T> {
// Add an event to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_event(&self, event: Event<T>) {
pub fn send_event(&self, event: Event<'static, T>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
@ -153,7 +156,7 @@ impl<T: 'static> Shared<T> {
// handle_event takes in events and either queues them or applies a callback
//
// It should only ever be called from send_event
fn handle_event(&self, event: Event<T>, control: &mut root::ControlFlow) {
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
let is_closed = self.is_closed();
match *self.0.runner.borrow_mut() {

View file

@ -1,5 +1,5 @@
use super::{backend, device, proxy::Proxy, runner, window};
use crate::dpi::LogicalSize;
use crate::dpi::{PhysicalSize, Size};
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
@ -28,7 +28,7 @@ impl<T> WindowTarget<T> {
Proxy::new(self.runner.clone())
}
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
pub fn run(&self, event_handler: Box<dyn FnMut(Event<'static, T>, &mut ControlFlow)>) {
self.runner.set_listener(event_handler);
}
@ -170,25 +170,27 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
let raw = canvas.raw().clone();
let mut intended_size = LogicalSize {
width: raw.width() as f64,
height: raw.height() as f64,
// The size to restore to after exiting fullscreen.
let mut intended_size = PhysicalSize {
width: raw.width() as u32,
height: raw.height() as u32,
};
canvas.on_fullscreen_change(move || {
// If the canvas is marked as fullscreen, it is moving *into* fullscreen
// If it is not, it is moving *out of* fullscreen
let new_size = if backend::is_fullscreen(&raw) {
intended_size = LogicalSize {
width: raw.width() as f64,
height: raw.height() as f64,
intended_size = PhysicalSize {
width: raw.width() as u32,
height: raw.height() as u32,
};
backend::window_size()
backend::window_size().to_physical(backend::hidpi_factor())
} else {
intended_size
};
raw.set_width(new_size.width as u32);
raw.set_height(new_size.height as u32);
backend::set_canvas_size(&raw, Size::Physical(new_size));
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::Resized(new_size),

View file

@ -10,7 +10,7 @@ impl Handle {
}
pub fn position(&self) -> PhysicalPosition<i32> {
PhysicalPosition { x: 0.0, y: 0.0 }
PhysicalPosition { x: 0, y: 0 }
}
pub fn name(&self) -> Option<String> {
@ -19,8 +19,8 @@ impl Handle {
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: 0.0,
height: 0.0,
width: 0,
height: 0,
}
}

View file

@ -1,5 +1,5 @@
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
@ -19,6 +19,7 @@ use stdweb::web::{
};
pub struct Canvas {
/// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: CanvasElement,
on_focus: Option<EventListenerHandle>,
on_blur: Option<EventListenerHandle>,
@ -82,23 +83,20 @@ impl Canvas {
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> (f64, f64) {
pub fn position(&self) -> LogicalPosition<f64> {
let bounds = self.raw.get_bounding_client_rect();
(bounds.get_x(), bounds.get_y())
LogicalPosition {
x: bounds.get_x(),
y: bounds.get_y(),
}
}
pub fn width(&self) -> f64 {
self.raw.width() as f64
}
pub fn height(&self) -> f64 {
self.raw.height() as f64
}
pub fn set_size(&self, size: LogicalSize<f64>) {
self.raw.set_width(size.width as u32);
self.raw.set_height(size.height as u32);
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: self.raw.width() as u32,
height: self.raw.height() as u32,
}
}
pub fn raw(&self) -> &CanvasElement {
@ -209,12 +207,13 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
{
// todo
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_position(&event).to_physical(super::hidpi_factor()),
event::mouse_modifiers(&event),
);
}));

View file

@ -5,7 +5,7 @@ mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtStdweb;
use crate::window::Window;
@ -41,6 +41,28 @@ pub fn window_size() -> LogicalSize<f64> {
LogicalSize { width, height }
}
pub fn hidpi_factor() -> f64 {
let window = window();
window.device_pixel_ratio()
}
pub fn set_canvas_size(raw: &CanvasElement, size: Size) {
use stdweb::*;
let hidpi_factor = hidpi_factor();
let physical_size = size.to_physical::<u32>(hidpi_factor);
let logical_size = size.to_logical::<f64>(hidpi_factor);
raw.set_width(physical_size.width);
raw.set_height(physical_size.height);
js! {
@{raw.as_ref()}.style.width = @{logical_size.width} + "px";
@{raw.as_ref()}.style.height = @{logical_size.height} + "px";
}
}
pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
match document().fullscreen_element() {
Some(elem) => {

View file

@ -1,5 +1,5 @@
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
@ -11,6 +11,7 @@ use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
pub struct Canvas {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: HtmlCanvasElement,
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
@ -80,23 +81,20 @@ impl Canvas {
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> (f64, f64) {
pub fn position(&self) -> LogicalPosition<f64> {
let bounds = self.raw.get_bounding_client_rect();
(bounds.x(), bounds.y())
LogicalPosition {
x: bounds.x(),
y: bounds.y(),
}
}
pub fn width(&self) -> f64 {
self.raw.width() as f64
}
pub fn height(&self) -> f64 {
self.raw.height() as f64
}
pub fn set_size(&self, size: LogicalSize<f64>) {
self.raw.set_width(size.width as u32);
self.raw.set_height(size.height as u32);
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: self.raw.width(),
height: self.raw.height(),
}
}
pub fn raw(&self) -> &HtmlCanvasElement {
@ -218,12 +216,12 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
{
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_position(&event).to_physical(super::hidpi_factor()),
event::mouse_modifiers(&event),
);
}));

View file

@ -5,7 +5,7 @@ mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtWebSys;
use crate::window::Window;
use wasm_bindgen::{closure::Closure, JsCast};
@ -56,6 +56,29 @@ pub fn window_size() -> LogicalSize<f64> {
LogicalSize { width, height }
}
pub fn hidpi_factor() -> f64 {
let window = web_sys::window().expect("Failed to obtain window");
window.device_pixel_ratio()
}
pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) {
let hidpi_factor = hidpi_factor();
let physical_size = size.to_physical::<u32>(hidpi_factor);
let logical_size = size.to_logical::<f64>(hidpi_factor);
raw.set_width(physical_size.width);
raw.set_height(physical_size.height);
let style = raw.style();
style
.set_property("width", &format!("{}px", logical_size.width))
.expect("Failed to set canvas width");
style
.set_property("height", &format!("{}px", logical_size.height))
.expect("Failed to set canvas height");
}
pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
let window = window().expect("Failed to obtain window");
let document = window.document().expect("Failed to obtain document");

View file

@ -1,4 +1,4 @@
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH;
@ -15,7 +15,6 @@ use std::collections::VecDeque;
pub struct Window {
canvas: backend::Canvas,
previous_pointer: RefCell<&'static str>,
position: RefCell<LogicalPosition<f64>>,
id: Id,
register_redraw_request: Box<dyn Fn()>,
}
@ -39,15 +38,14 @@ impl Window {
let window = Window {
canvas,
previous_pointer: RefCell::new("auto"),
position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }),
id,
register_redraw_request,
};
window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize {
window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize {
width: 1024.0,
height: 768.0,
}));
})));
window.set_title(&attr.title);
window.set_maximized(attr.maximized);
window.set_visible(attr.visible);
@ -72,18 +70,17 @@ impl Window {
(self.register_redraw_request)();
}
pub fn outer_position(&self) -> Result<LogicalPosition<f64>, NotSupportedError> {
let (x, y) = self.canvas.position();
Ok(LogicalPosition { x, y })
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Ok(self.canvas.position().to_physical(self.hidpi_factor()))
}
pub fn inner_position(&self) -> Result<LogicalPosition<f64>, NotSupportedError> {
Ok(*self.position.borrow())
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
// Note: the canvas element has no window decorations, so this is equal to `outer_position`.
self.outer_position()
}
pub fn set_outer_position(&self, position: LogicalPosition<f64>) {
*self.position.borrow_mut() = position;
pub fn set_outer_position(&self, position: Position) {
let position = position.to_logical::<f64>(self.hidpi_factor());
self.canvas.set_attribute("position", "fixed");
self.canvas.set_attribute("left", &position.x.to_string());
@ -91,33 +88,28 @@ impl Window {
}
#[inline]
pub fn inner_size(&self) -> LogicalSize<f64> {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64,
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.canvas.size()
}
#[inline]
pub fn outer_size(&self) -> LogicalSize<f64> {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64,
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
// Note: the canvas element has no window decorations, so this is equal to `inner_size`.
self.inner_size()
}
#[inline]
pub fn set_inner_size(&self, size: LogicalSize<f64>) {
self.canvas.set_size(size);
pub fn set_inner_size(&self, size: Size) {
backend::set_canvas_size(self.canvas.raw(), size);
}
#[inline]
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
// Intentionally a no-op: users can't resize canvas elements
}
@ -128,7 +120,7 @@ impl Window {
#[inline]
pub fn hidpi_factor(&self) -> f64 {
1.0
super::backend::hidpi_factor()
}
#[inline]
@ -178,10 +170,7 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(
&self,
_position: LogicalPosition<f64>,
) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
// Intentionally a no-op, as the web does not support setting cursor positions
Ok(())
}
@ -246,7 +235,7 @@ impl Window {
}
#[inline]
pub fn set_ime_position(&self, _position: LogicalPosition<f64>) {
pub fn set_ime_position(&self, _position: Position) {
// Currently a no-op as it does not seem there is good support for this on web
}

View file

@ -419,6 +419,8 @@ impl Window {
///
/// - **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.
/// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the
/// same value as `outer_position`._
///
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline]
@ -440,6 +442,7 @@ impl Window {
///
/// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// window in the screen space coordinate system.
/// - **Web:** Returns the top-left coordinates relative to the viewport.
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
self.window.outer_position()
@ -454,6 +457,7 @@ impl Window {
///
/// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the
/// window in the screen space coordinate system.
/// - **Web:** Sets the top-left coordinates relative to the viewport.
#[inline]
pub fn set_outer_position<P: Into<Position>>(&self, position: P) {
self.window.set_outer_position(position.into())
@ -467,6 +471,7 @@ impl Window {
///
/// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's
/// [safe area] in screen space coordinates.
/// - **Web:** Returns the size of the canvas element.
///
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline]
@ -483,6 +488,7 @@ impl Window {
///
/// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size`
/// would mean for iOS.
/// - **Web:** Sets the size of the canvas element.
#[inline]
pub fn set_inner_size<S: Into<Size>>(&self, size: S) {
self.window.set_inner_size(size.into())
@ -497,6 +503,8 @@ impl Window {
///
/// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in
/// screen space coordinates.
/// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as
/// `inner_size`._
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
self.window.outer_size()