mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
Handle scale factor change on web-sys backend (#1690)
* Change web backend `event_handler` to without 'static lifetime * Refactor web runner and fix ControlFlow::Exit not being sticky * Impl. scaling change event for web-sys backend * Improve `dpi` docs regarding the web backend * Add changes to changelog * Update features.md
This commit is contained in:
parent
a2db4c0a32
commit
658a9a4ea8
|
@ -20,6 +20,8 @@
|
||||||
- On macOS, add `NSWindow.hasShadow` support.
|
- On macOS, add `NSWindow.hasShadow` support.
|
||||||
- On Web, fix vertical mouse wheel scrolling being inverted.
|
- On Web, fix vertical mouse wheel scrolling being inverted.
|
||||||
- On Web, implement mouse capturing for click-dragging out of the canvas.
|
- On Web, implement mouse capturing for click-dragging out of the canvas.
|
||||||
|
- On Web, fix `ControlFlow::Exit` not properly handled.
|
||||||
|
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
|
||||||
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
||||||
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
||||||
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
||||||
|
|
|
@ -182,9 +182,11 @@ Legend:
|
||||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
||||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|
||||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1|
|
||||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||||
|
|
||||||
|
\*1: `WindowEvent::ScaleFactorChanged` is not sent on `stdweb` backend.
|
||||||
|
|
||||||
### System information
|
### System information
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|
||||||
|
|
|
@ -89,6 +89,8 @@
|
||||||
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
|
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
|
||||||
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
|
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
|
||||||
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
|
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
|
||||||
|
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
|
||||||
|
//! both the screen scaling and the browser zoom level and can go below `1.0`.
|
||||||
//!
|
//!
|
||||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||||
|
@ -96,6 +98,7 @@
|
||||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
||||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
||||||
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
||||||
|
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
|
||||||
|
|
||||||
pub trait Pixel: Copy + Into<f64> {
|
pub trait Pixel: Copy + Into<f64> {
|
||||||
fn from_f64(f: f64) -> Self;
|
fn from_f64(f: f64) -> Self;
|
||||||
|
|
|
@ -72,8 +72,11 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ControlFlow {
|
pub enum ControlFlow {
|
||||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||||
/// whether or not new events are available to process. For web, events are sent when
|
/// whether or not new events are available to process.
|
||||||
/// `requestAnimationFrame` fires.
|
///
|
||||||
|
/// For web, events are queued and usually sent when `requestAnimationFrame` fires but sometimes
|
||||||
|
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
|
||||||
|
/// example when the scaling of the page has changed.
|
||||||
Poll,
|
Poll,
|
||||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||||
Wait,
|
Wait,
|
||||||
|
|
|
@ -28,8 +28,7 @@ impl<T> EventLoop<T> {
|
||||||
|
|
||||||
pub fn run<F>(self, mut event_handler: F) -> !
|
pub fn run<F>(self, mut event_handler: F) -> !
|
||||||
where
|
where
|
||||||
F: 'static
|
F: 'static + FnMut(Event<'_, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
|
||||||
+ FnMut(Event<'static, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
|
|
||||||
{
|
{
|
||||||
let target = root::EventLoopWindowTarget {
|
let target = root::EventLoopWindowTarget {
|
||||||
p: self.elw.p.clone(),
|
p: self.elw.p.clone(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{backend, state::State};
|
use super::{super::ScaleChangeArgs, backend, state::State};
|
||||||
use crate::event::{Event, StartCause};
|
use crate::event::{Event, StartCause};
|
||||||
use crate::event_loop as root;
|
use crate::event_loop as root;
|
||||||
use crate::window::WindowId;
|
use crate::window::WindowId;
|
||||||
|
@ -24,23 +24,60 @@ pub struct Execution<T: 'static> {
|
||||||
runner: RefCell<Option<Runner<T>>>,
|
runner: RefCell<Option<Runner<T>>>,
|
||||||
events: RefCell<VecDeque<Event<'static, T>>>,
|
events: RefCell<VecDeque<Event<'static, T>>>,
|
||||||
id: RefCell<u32>,
|
id: RefCell<u32>,
|
||||||
|
all_canvases: RefCell<Vec<(WindowId, backend::RawCanvasType)>>,
|
||||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||||
|
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Runner<T: 'static> {
|
struct Runner<T: 'static> {
|
||||||
state: State,
|
state: State,
|
||||||
is_busy: bool,
|
is_busy: bool,
|
||||||
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Runner<T> {
|
impl<T: 'static> Runner<T> {
|
||||||
pub fn new(event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>) -> Self {
|
pub fn new(event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>) -> Self {
|
||||||
Runner {
|
Runner {
|
||||||
state: State::Init,
|
state: State::Init,
|
||||||
is_busy: false,
|
is_busy: false,
|
||||||
event_handler,
|
event_handler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the cooresponding `StartCause` for the current `state`, or `None`
|
||||||
|
/// when in `Exit` state.
|
||||||
|
fn maybe_start_cause(&self) -> Option<StartCause> {
|
||||||
|
Some(match self.state {
|
||||||
|
State::Init => StartCause::Init,
|
||||||
|
State::Poll { .. } => StartCause::Poll,
|
||||||
|
State::Wait { start } => StartCause::WaitCancelled {
|
||||||
|
start,
|
||||||
|
requested_resume: None,
|
||||||
|
},
|
||||||
|
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||||
|
start,
|
||||||
|
requested_resume: Some(end),
|
||||||
|
},
|
||||||
|
State::Exit => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) {
|
||||||
|
let is_closed = *control == root::ControlFlow::Exit;
|
||||||
|
|
||||||
|
// An event is being processed, so the runner should be marked busy
|
||||||
|
self.is_busy = true;
|
||||||
|
|
||||||
|
(self.event_handler)(event, control);
|
||||||
|
|
||||||
|
// Maintain closed state, even if the callback changes it
|
||||||
|
if is_closed {
|
||||||
|
*control = root::ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An event is no longer being processed
|
||||||
|
self.is_busy = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Shared<T> {
|
impl<T: 'static> Shared<T> {
|
||||||
|
@ -49,16 +86,22 @@ impl<T: 'static> Shared<T> {
|
||||||
runner: RefCell::new(None),
|
runner: RefCell::new(None),
|
||||||
events: RefCell::new(VecDeque::new()),
|
events: RefCell::new(VecDeque::new()),
|
||||||
id: RefCell::new(0),
|
id: RefCell::new(0),
|
||||||
|
all_canvases: RefCell::new(Vec::new()),
|
||||||
redraw_pending: RefCell::new(HashSet::new()),
|
redraw_pending: RefCell::new(HashSet::new()),
|
||||||
|
scale_change_detector: RefCell::new(None),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_canvas(&self, id: WindowId, canvas: backend::RawCanvasType) {
|
||||||
|
self.0.all_canvases.borrow_mut().push((id, canvas));
|
||||||
|
}
|
||||||
|
|
||||||
// Set the event callback to use for the event loop runner
|
// 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
|
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
||||||
// over a RootEventLoopWindowTarget reference
|
// over a RootEventLoopWindowTarget reference
|
||||||
pub fn set_listener(
|
pub fn set_listener(
|
||||||
&self,
|
&self,
|
||||||
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
|
||||||
) {
|
) {
|
||||||
self.0.runner.replace(Some(Runner::new(event_handler)));
|
self.0.runner.replace(Some(Runner::new(event_handler)));
|
||||||
self.init();
|
self.init();
|
||||||
|
@ -67,6 +110,14 @@ impl<T: 'static> Shared<T> {
|
||||||
backend::on_unload(move || close_instance.handle_unload());
|
backend::on_unload(move || close_instance.handle_unload());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
|
{
|
||||||
|
*self.0.scale_change_detector.borrow_mut() =
|
||||||
|
Some(backend::ScaleChangeDetector::new(handler));
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a strictly increasing ID
|
// Generate a strictly increasing ID
|
||||||
// This is used to differentiate windows when handling events
|
// This is used to differentiate windows when handling events
|
||||||
pub fn generate_id(&self) -> u32 {
|
pub fn generate_id(&self) -> u32 {
|
||||||
|
@ -138,25 +189,15 @@ impl<T: 'static> Shared<T> {
|
||||||
}
|
}
|
||||||
// At this point, we know this is a fresh set of events
|
// At this point, we know this is a fresh set of events
|
||||||
// Now we determine why new events are incoming, and handle the events
|
// Now we determine why new events are incoming, and handle the events
|
||||||
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
|
let start_cause = match (self.0.runner.borrow().as_ref())
|
||||||
match runner.state {
|
.unwrap_or_else(|| {
|
||||||
State::Init => StartCause::Init,
|
unreachable!("The runner cannot process events when it is not attached")
|
||||||
State::Poll { .. } => StartCause::Poll,
|
})
|
||||||
State::Wait { start } => StartCause::WaitCancelled {
|
.maybe_start_cause()
|
||||||
start,
|
{
|
||||||
requested_resume: None,
|
Some(c) => c,
|
||||||
},
|
// If we're in the exit state, don't do event processing
|
||||||
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
None => return,
|
||||||
start,
|
|
||||||
requested_resume: Some(end),
|
|
||||||
},
|
|
||||||
State::Exit => {
|
|
||||||
// If we're in the exit state, don't do event processing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!("The runner cannot process events when it is not attached");
|
|
||||||
};
|
};
|
||||||
// Take the start event, then the events provided to this function, and run an iteration of
|
// Take the start event, then the events provided to this function, and run an iteration of
|
||||||
// the event loop
|
// the event loop
|
||||||
|
@ -191,37 +232,107 @@ impl<T: 'static> Shared<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) {
|
||||||
|
let start_cause = match (self.0.runner.borrow().as_ref())
|
||||||
|
.unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner"))
|
||||||
|
.maybe_start_cause()
|
||||||
|
{
|
||||||
|
Some(c) => c,
|
||||||
|
// If we're in the exit state, don't do event processing
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let mut control = self.current_control_flow();
|
||||||
|
|
||||||
|
// Handle the start event and all other events in the queue.
|
||||||
|
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
||||||
|
|
||||||
|
// Now handle the `ScaleFactorChanged` events.
|
||||||
|
for &(id, ref canvas) in &*self.0.all_canvases.borrow() {
|
||||||
|
// First, we send the `ScaleFactorChanged` event:
|
||||||
|
let current_size = crate::dpi::PhysicalSize {
|
||||||
|
width: canvas.width() as u32,
|
||||||
|
height: canvas.height() as u32,
|
||||||
|
};
|
||||||
|
let logical_size = current_size.to_logical::<f64>(old_scale);
|
||||||
|
let mut new_size = logical_size.to_physical(new_scale);
|
||||||
|
self.handle_single_event_sync(
|
||||||
|
Event::WindowEvent {
|
||||||
|
window_id: id,
|
||||||
|
event: crate::event::WindowEvent::ScaleFactorChanged {
|
||||||
|
scale_factor: new_scale,
|
||||||
|
new_inner_size: &mut new_size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut control,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then we resize the canvas to the new size and send a `Resized` event:
|
||||||
|
backend::set_canvas_size(canvas, crate::dpi::Size::Physical(new_size));
|
||||||
|
self.handle_single_event_sync(
|
||||||
|
Event::WindowEvent {
|
||||||
|
window_id: id,
|
||||||
|
event: crate::event::WindowEvent::Resized(new_size),
|
||||||
|
},
|
||||||
|
&mut control,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||||
|
|
||||||
|
// Discard all the pending redraw as we shall just redraw all windows.
|
||||||
|
self.0.redraw_pending.borrow_mut().clear();
|
||||||
|
for &(window_id, _) in &*self.0.all_canvases.borrow() {
|
||||||
|
self.handle_event(Event::RedrawRequested(window_id), &mut control);
|
||||||
|
}
|
||||||
|
self.handle_event(Event::RedrawEventsCleared, &mut control);
|
||||||
|
|
||||||
|
self.apply_control_flow(control);
|
||||||
|
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||||
|
// event should be emitted
|
||||||
|
if self.is_closed() {
|
||||||
|
self.handle_event(Event::LoopDestroyed, &mut control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_unload(&self) {
|
fn handle_unload(&self) {
|
||||||
self.apply_control_flow(root::ControlFlow::Exit);
|
self.apply_control_flow(root::ControlFlow::Exit);
|
||||||
let mut control = self.current_control_flow();
|
let mut control = self.current_control_flow();
|
||||||
self.handle_event(Event::LoopDestroyed, &mut control);
|
self.handle_event(Event::LoopDestroyed, &mut control);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle_event takes in events and either queues them or applies a callback
|
// handle_single_event_sync takes in an event and handles it synchronously.
|
||||||
//
|
//
|
||||||
// It should only ever be called from send_event
|
// It should only ever be called from `scale_changed`.
|
||||||
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
|
fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut root::ControlFlow) {
|
||||||
let is_closed = self.is_closed();
|
if self.is_closed() {
|
||||||
|
*control = root::ControlFlow::Exit;
|
||||||
|
}
|
||||||
match *self.0.runner.borrow_mut() {
|
match *self.0.runner.borrow_mut() {
|
||||||
Some(ref mut runner) => {
|
Some(ref mut runner) => {
|
||||||
// An event is being processed, so the runner should be marked busy
|
runner.handle_single_event(event, control);
|
||||||
runner.is_busy = true;
|
}
|
||||||
|
_ => panic!("Cannot handle event synchronously without a runner"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(runner.event_handler)(event, control);
|
// handle_event takes in events and either queues them or applies a callback
|
||||||
|
//
|
||||||
// Maintain closed state, even if the callback changes it
|
// It should only ever be called from `run_until_cleared` and `scale_changed`.
|
||||||
if is_closed {
|
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
|
||||||
*control = root::ControlFlow::Exit;
|
if self.is_closed() {
|
||||||
}
|
*control = root::ControlFlow::Exit;
|
||||||
|
}
|
||||||
// An event is no longer being processed
|
match *self.0.runner.borrow_mut() {
|
||||||
runner.is_busy = false;
|
Some(ref mut runner) => {
|
||||||
|
runner.handle_single_event(event, control);
|
||||||
}
|
}
|
||||||
// If an event is being handled without a runner somehow, add it to the event queue so
|
// If an event is being handled without a runner somehow, add it to the event queue so
|
||||||
// it will eventually be processed
|
// it will eventually be processed
|
||||||
_ => self.0.events.borrow_mut().push_back(event),
|
_ => self.0.events.borrow_mut().push_back(event),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_closed = *control == root::ControlFlow::Exit;
|
||||||
|
|
||||||
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
|
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
|
||||||
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
||||||
if !is_closed && self.0.runner.borrow().is_some() {
|
if !is_closed && self.0.runner.borrow().is_some() {
|
||||||
|
|
|
@ -29,8 +29,12 @@ impl<T> WindowTarget<T> {
|
||||||
Proxy::new(self.runner.clone())
|
Proxy::new(self.runner.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, event_handler: Box<dyn FnMut(Event<'static, T>, &mut ControlFlow)>) {
|
pub fn run(&self, event_handler: Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>) {
|
||||||
self.runner.set_listener(event_handler);
|
self.runner.set_listener(event_handler);
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
self.runner.set_on_scale_change(move |arg| {
|
||||||
|
runner.handle_scale_changed(arg.old_scale, arg.new_scale)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_id(&self) -> window::Id {
|
pub fn generate_id(&self) -> window::Id {
|
||||||
|
@ -40,6 +44,7 @@ impl<T> WindowTarget<T> {
|
||||||
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
|
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
|
||||||
let runner = self.runner.clone();
|
let runner = self.runner.clone();
|
||||||
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
||||||
|
runner.add_canvas(WindowId(id), canvas.raw().clone());
|
||||||
|
|
||||||
canvas.on_blur(move || {
|
canvas.on_blur(move || {
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
|
|
|
@ -46,3 +46,9 @@ pub use self::window::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct ScaleChangeArgs {
|
||||||
|
old_scale: f64,
|
||||||
|
new_scale: f64,
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod scaling;
|
||||||
mod timeout;
|
mod timeout;
|
||||||
|
|
||||||
pub use self::canvas::Canvas;
|
pub use self::canvas::Canvas;
|
||||||
|
pub use self::scaling::ScaleChangeDetector;
|
||||||
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
||||||
|
|
||||||
use crate::dpi::{LogicalSize, Size};
|
use crate::dpi::{LogicalSize, Size};
|
||||||
|
|
13
src/platform_impl/web/stdweb/scaling.rs
Normal file
13
src/platform_impl/web/stdweb/scaling.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use super::super::ScaleChangeArgs;
|
||||||
|
|
||||||
|
pub struct ScaleChangeDetector(());
|
||||||
|
|
||||||
|
impl ScaleChangeDetector {
|
||||||
|
pub(crate) fn new<F>(_handler: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
|
{
|
||||||
|
// TODO: Stub, unimplemented (see web_sys for reference).
|
||||||
|
Self(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod scaling;
|
||||||
mod timeout;
|
mod timeout;
|
||||||
|
|
||||||
pub use self::canvas::Canvas;
|
pub use self::canvas::Canvas;
|
||||||
|
pub use self::scaling::ScaleChangeDetector;
|
||||||
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
||||||
|
|
||||||
use crate::dpi::{LogicalSize, Size};
|
use crate::dpi::{LogicalSize, Size};
|
||||||
|
|
110
src/platform_impl/web/web_sys/scaling.rs
Normal file
110
src/platform_impl/web/web_sys/scaling.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use super::super::ScaleChangeArgs;
|
||||||
|
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||||
|
use web_sys::{MediaQueryList, MediaQueryListEvent};
|
||||||
|
|
||||||
|
pub struct ScaleChangeDetector(Rc<RefCell<ScaleChangeDetectorInternal>>);
|
||||||
|
|
||||||
|
impl ScaleChangeDetector {
|
||||||
|
pub(crate) fn new<F>(handler: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
|
{
|
||||||
|
Self(ScaleChangeDetectorInternal::new(handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a helper type to help manage the `MediaQueryList` used for detecting
|
||||||
|
/// changes of the `devicePixelRatio`.
|
||||||
|
struct ScaleChangeDetectorInternal {
|
||||||
|
callback: Box<dyn FnMut(ScaleChangeArgs)>,
|
||||||
|
closure: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
||||||
|
mql: Option<MediaQueryList>,
|
||||||
|
last_scale: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScaleChangeDetectorInternal {
|
||||||
|
fn new<F>(handler: F) -> Rc<RefCell<Self>>
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
|
{
|
||||||
|
let current_scale = super::scale_factor();
|
||||||
|
let new_self = Rc::new(RefCell::new(Self {
|
||||||
|
callback: Box::new(handler),
|
||||||
|
closure: None,
|
||||||
|
mql: None,
|
||||||
|
last_scale: current_scale,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let cloned_self = new_self.clone();
|
||||||
|
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
|
||||||
|
cloned_self.borrow_mut().handler(event)
|
||||||
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
|
||||||
|
let mql = Self::create_mql(&closure);
|
||||||
|
{
|
||||||
|
let mut borrowed_self = new_self.borrow_mut();
|
||||||
|
borrowed_self.closure = Some(closure);
|
||||||
|
borrowed_self.mql = mql;
|
||||||
|
}
|
||||||
|
new_self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_mql(closure: &Closure<dyn FnMut(MediaQueryListEvent)>) -> Option<MediaQueryList> {
|
||||||
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
let current_scale = super::scale_factor();
|
||||||
|
// This media query initially matches the current `devicePixelRatio`.
|
||||||
|
// We add 0.0001 to the lower and upper bounds such that it won't fail
|
||||||
|
// due to floating point precision limitations.
|
||||||
|
let media_query = format!(
|
||||||
|
"(min-resolution: {:.4}dppx) and (max-resolution: {:.4}dppx)",
|
||||||
|
current_scale - 0.0001,
|
||||||
|
current_scale + 0.0001,
|
||||||
|
);
|
||||||
|
window
|
||||||
|
.match_media(&media_query)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|mql| {
|
||||||
|
assert_eq!(mql.matches(), true);
|
||||||
|
mql.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref()))
|
||||||
|
.map(|_| mql)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler(&mut self, event: MediaQueryListEvent) {
|
||||||
|
assert_eq!(event.matches(), false);
|
||||||
|
let closure = self
|
||||||
|
.closure
|
||||||
|
.as_ref()
|
||||||
|
.expect("DevicePixelRatioChangeDetector::closure should not be None");
|
||||||
|
let mql = self
|
||||||
|
.mql
|
||||||
|
.take()
|
||||||
|
.expect("DevicePixelRatioChangeDetector::mql should not be None");
|
||||||
|
mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref()))
|
||||||
|
.expect("Failed to remove listener from MediaQueryList");
|
||||||
|
let new_scale = super::scale_factor();
|
||||||
|
(self.callback)(ScaleChangeArgs {
|
||||||
|
old_scale: self.last_scale,
|
||||||
|
new_scale,
|
||||||
|
});
|
||||||
|
let new_mql = Self::create_mql(closure);
|
||||||
|
self.mql = new_mql;
|
||||||
|
self.last_scale = new_scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScaleChangeDetectorInternal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
match (self.closure.as_ref(), self.mql.as_ref()) {
|
||||||
|
(Some(closure), Some(mql)) => {
|
||||||
|
let _ =
|
||||||
|
mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue