Skip to content

Commit

Permalink
Implement hidpi for web platform (#1233)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tangmi authored and Osspial committed Jan 4, 2020
1 parent afd5aea commit 58eb18b
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 91 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ version = "0.3.22"
optional = true
features = [
'console',
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
Expand Down
3 changes: 2 additions & 1 deletion src/platform_impl/web/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
21 changes: 12 additions & 9 deletions src/platform_impl/web/event_loop/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ 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 {
Shared(self.0.clone())
}
}

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,
Expand All @@ -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));

Expand All @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
24 changes: 13 additions & 11 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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),
Expand Down
6 changes: 3 additions & 3 deletions src/platform_impl/web/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -19,8 +19,8 @@ impl Handle {

pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: 0.0,
height: 0.0,
width: 0,
height: 0,
}
}

Expand Down
31 changes: 15 additions & 16 deletions src/platform_impl/web/stdweb/canvas.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
);
}));
Expand Down
24 changes: 23 additions & 1 deletion src/platform_impl/web/stdweb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) => {
Expand Down
30 changes: 14 additions & 16 deletions src/platform_impl/web/web_sys/canvas.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)>>,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
);
}));
Expand Down
25 changes: 24 additions & 1 deletion src/platform_impl/web/web_sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 58eb18b

Please sign in to comment.