mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-11 21:31:29 +11:00
Add cleanup code to web backend, mostly web-sys (#1715)
* web-sys: Impl. event listeners removal for canvas * web-sys: Impl. media query listeners cleanup * web: Emit WindowEvent::Destroyed after Window is dropped * web-sys: Fix unload event closure being dropped early * web: Impl. cleanup on ControlFlow::Exit - Drops the Runner, which causes the event handler closure to be dropped. - (web-sys only:) Remove event listeners from DOM. * web: Do not remove canvas from DOM when dropping Window The canvas was inserted by the user, so it should be up to the user whether the canvas should be removed. * Update changelog
This commit is contained in:
parent
1c97a310b1
commit
47e7aa4209
|
@ -35,6 +35,11 @@
|
||||||
- Deprecate the stdweb backend, to be removed in a future release
|
- Deprecate the stdweb backend, to be removed in a future release
|
||||||
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
|
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
|
||||||
- Added `Asterisk` and `Plus` virtual key codes.
|
- Added `Asterisk` and `Plus` virtual key codes.
|
||||||
|
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
|
||||||
|
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
|
||||||
|
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
|
||||||
|
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
|
||||||
|
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
|
||||||
|
|
||||||
# 0.22.2 (2020-05-16)
|
# 0.22.2 (2020-05-16)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
||||||
clone::Clone,
|
clone::Clone,
|
||||||
collections::{HashSet, VecDeque},
|
collections::{HashSet, VecDeque},
|
||||||
iter,
|
iter,
|
||||||
rc::Rc,
|
rc::{Rc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Shared<T: 'static>(Rc<Execution<T>>);
|
pub struct Shared<T: 'static>(Rc<Execution<T>>);
|
||||||
|
@ -21,12 +21,34 @@ impl<T> Clone for Shared<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Execution<T: 'static> {
|
pub struct Execution<T: 'static> {
|
||||||
runner: RefCell<Option<Runner<T>>>,
|
runner: RefCell<RunnerEnum<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)>>,
|
all_canvases: RefCell<Vec<(WindowId, Weak<RefCell<backend::Canvas>>)>>,
|
||||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||||
|
destroy_pending: RefCell<VecDeque<WindowId>>,
|
||||||
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
|
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
|
||||||
|
unload_event_handle: RefCell<Option<backend::UnloadEventHandle>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RunnerEnum<T: 'static> {
|
||||||
|
/// The `EventLoop` is created but not being run.
|
||||||
|
Pending,
|
||||||
|
/// The `EventLoop` is being run.
|
||||||
|
Running(Runner<T>),
|
||||||
|
/// The `EventLoop` is exited after being started with `EventLoop::run`. Since
|
||||||
|
/// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain
|
||||||
|
/// that this event loop will never be run again.
|
||||||
|
Destroyed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> RunnerEnum<T> {
|
||||||
|
fn maybe_runner(&self) -> Option<&Runner<T>> {
|
||||||
|
match self {
|
||||||
|
RunnerEnum::Running(runner) => Some(runner),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Runner<T: 'static> {
|
struct Runner<T: 'static> {
|
||||||
|
@ -83,17 +105,26 @@ impl<T: 'static> Runner<T> {
|
||||||
impl<T: 'static> Shared<T> {
|
impl<T: 'static> Shared<T> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Shared(Rc::new(Execution {
|
Shared(Rc::new(Execution {
|
||||||
runner: RefCell::new(None),
|
runner: RefCell::new(RunnerEnum::Pending),
|
||||||
events: RefCell::new(VecDeque::new()),
|
events: RefCell::new(VecDeque::new()),
|
||||||
id: RefCell::new(0),
|
id: RefCell::new(0),
|
||||||
all_canvases: RefCell::new(Vec::new()),
|
all_canvases: RefCell::new(Vec::new()),
|
||||||
redraw_pending: RefCell::new(HashSet::new()),
|
redraw_pending: RefCell::new(HashSet::new()),
|
||||||
|
destroy_pending: RefCell::new(VecDeque::new()),
|
||||||
scale_change_detector: RefCell::new(None),
|
scale_change_detector: RefCell::new(None),
|
||||||
|
unload_event_handle: RefCell::new(None),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_canvas(&self, id: WindowId, canvas: backend::RawCanvasType) {
|
pub fn add_canvas(&self, id: WindowId, canvas: &Rc<RefCell<backend::Canvas>>) {
|
||||||
self.0.all_canvases.borrow_mut().push((id, canvas));
|
self.0
|
||||||
|
.all_canvases
|
||||||
|
.borrow_mut()
|
||||||
|
.push((id, Rc::downgrade(canvas)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notify_destroy_window(&self, id: WindowId) {
|
||||||
|
self.0.destroy_pending.borrow_mut().push_back(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the event callback to use for the event loop runner
|
// Set the event callback to use for the event loop runner
|
||||||
|
@ -103,11 +134,16 @@ impl<T: 'static> Shared<T> {
|
||||||
&self,
|
&self,
|
||||||
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
|
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
|
||||||
) {
|
) {
|
||||||
self.0.runner.replace(Some(Runner::new(event_handler)));
|
{
|
||||||
|
let mut runner = self.0.runner.borrow_mut();
|
||||||
|
assert!(matches!(*runner, RunnerEnum::Pending));
|
||||||
|
*runner = RunnerEnum::Running(Runner::new(event_handler));
|
||||||
|
}
|
||||||
self.init();
|
self.init();
|
||||||
|
|
||||||
let close_instance = self.clone();
|
let close_instance = self.clone();
|
||||||
backend::on_unload(move || close_instance.handle_unload());
|
*self.0.unload_event_handle.borrow_mut() =
|
||||||
|
Some(backend::on_unload(move || close_instance.handle_unload()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
|
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
|
||||||
|
@ -169,7 +205,8 @@ impl<T: 'static> Shared<T> {
|
||||||
}
|
}
|
||||||
// If we can run the event processing right now, or need to queue this and wait for later
|
// If we can run the event processing right now, or need to queue this and wait for later
|
||||||
let mut process_immediately = true;
|
let mut process_immediately = true;
|
||||||
if let Some(ref runner) = &*self.0.runner.borrow() {
|
match &*self.0.runner.borrow() {
|
||||||
|
RunnerEnum::Running(ref runner) => {
|
||||||
// If we're currently polling, queue this and wait for the poll() method to be called
|
// If we're currently polling, queue this and wait for the poll() method to be called
|
||||||
if let State::Poll { .. } = runner.state {
|
if let State::Poll { .. } = runner.state {
|
||||||
process_immediately = false;
|
process_immediately = false;
|
||||||
|
@ -178,10 +215,14 @@ impl<T: 'static> Shared<T> {
|
||||||
if runner.is_busy {
|
if runner.is_busy {
|
||||||
process_immediately = false;
|
process_immediately = false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
RunnerEnum::Pending => {
|
||||||
// The runner still hasn't been attached: queue this event and wait for it to be
|
// The runner still hasn't been attached: queue this event and wait for it to be
|
||||||
process_immediately = false;
|
process_immediately = false;
|
||||||
}
|
}
|
||||||
|
// This is unreachable since `self.is_closed() == true`.
|
||||||
|
RunnerEnum::Destroyed => unreachable!(),
|
||||||
|
}
|
||||||
if !process_immediately {
|
if !process_immediately {
|
||||||
// Queue these events to look at later
|
// Queue these events to look at later
|
||||||
self.0.events.borrow_mut().extend(events);
|
self.0.events.borrow_mut().extend(events);
|
||||||
|
@ -189,7 +230,7 @@ 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 = match (self.0.runner.borrow().as_ref())
|
let start_cause = match (self.0.runner.borrow().maybe_runner())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
unreachable!("The runner cannot process events when it is not attached")
|
unreachable!("The runner cannot process events when it is not attached")
|
||||||
})
|
})
|
||||||
|
@ -206,6 +247,26 @@ impl<T: 'static> Shared<T> {
|
||||||
self.run_until_cleared(events);
|
self.run_until_cleared(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the destroy-pending windows. This should only be called from
|
||||||
|
// `run_until_cleared` and `handle_scale_changed`, somewhere between emitting
|
||||||
|
// `NewEvents` and `MainEventsCleared`.
|
||||||
|
fn process_destroy_pending_windows(&self, control: &mut root::ControlFlow) {
|
||||||
|
while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() {
|
||||||
|
self.0
|
||||||
|
.all_canvases
|
||||||
|
.borrow_mut()
|
||||||
|
.retain(|&(item_id, _)| item_id != id);
|
||||||
|
self.handle_event(
|
||||||
|
Event::WindowEvent {
|
||||||
|
window_id: id,
|
||||||
|
event: crate::event::WindowEvent::Destroyed,
|
||||||
|
},
|
||||||
|
control,
|
||||||
|
);
|
||||||
|
self.0.redraw_pending.borrow_mut().remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Given the set of new events, run the event loop until the main events and redraw events are
|
// Given the set of new events, run the event loop until the main events and redraw events are
|
||||||
// cleared
|
// cleared
|
||||||
//
|
//
|
||||||
|
@ -215,6 +276,7 @@ impl<T: 'static> Shared<T> {
|
||||||
for event in events {
|
for event in events {
|
||||||
self.handle_event(event, &mut control);
|
self.handle_event(event, &mut control);
|
||||||
}
|
}
|
||||||
|
self.process_destroy_pending_windows(&mut control);
|
||||||
self.handle_event(Event::MainEventsCleared, &mut control);
|
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||||
|
|
||||||
// Collect all of the redraw events to avoid double-locking the RefCell
|
// Collect all of the redraw events to avoid double-locking the RefCell
|
||||||
|
@ -228,12 +290,17 @@ impl<T: 'static> Shared<T> {
|
||||||
// If the event loop is closed, it has been closed this iteration and now the closing
|
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||||
// event should be emitted
|
// event should be emitted
|
||||||
if self.is_closed() {
|
if self.is_closed() {
|
||||||
self.handle_event(Event::LoopDestroyed, &mut control);
|
self.handle_loop_destroyed(&mut control);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) {
|
pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) {
|
||||||
let start_cause = match (self.0.runner.borrow().as_ref())
|
// If there aren't any windows, then there is nothing to do here.
|
||||||
|
if self.0.all_canvases.borrow().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_cause = match (self.0.runner.borrow().maybe_runner())
|
||||||
.unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner"))
|
.unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner"))
|
||||||
.maybe_start_cause()
|
.maybe_start_cause()
|
||||||
{
|
{
|
||||||
|
@ -246,8 +313,18 @@ impl<T: 'static> Shared<T> {
|
||||||
// Handle the start event and all other events in the queue.
|
// Handle the start event and all other events in the queue.
|
||||||
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
||||||
|
|
||||||
|
// It is possible for windows to be dropped before this point. We don't
|
||||||
|
// want to send `ScaleFactorChanged` for destroyed windows, so we process
|
||||||
|
// the destroy-pending windows here.
|
||||||
|
self.process_destroy_pending_windows(&mut control);
|
||||||
|
|
||||||
// Now handle the `ScaleFactorChanged` events.
|
// Now handle the `ScaleFactorChanged` events.
|
||||||
for &(id, ref canvas) in &*self.0.all_canvases.borrow() {
|
for &(id, ref canvas) in &*self.0.all_canvases.borrow() {
|
||||||
|
let canvas = match canvas.upgrade() {
|
||||||
|
Some(rc) => rc.borrow().raw().clone(),
|
||||||
|
// This shouldn't happen, but just in case...
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
// First, we send the `ScaleFactorChanged` event:
|
// First, we send the `ScaleFactorChanged` event:
|
||||||
let current_size = crate::dpi::PhysicalSize {
|
let current_size = crate::dpi::PhysicalSize {
|
||||||
width: canvas.width() as u32,
|
width: canvas.width() as u32,
|
||||||
|
@ -267,7 +344,7 @@ impl<T: 'static> Shared<T> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then we resize the canvas to the new size and send a `Resized` event:
|
// 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));
|
backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size));
|
||||||
self.handle_single_event_sync(
|
self.handle_single_event_sync(
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
window_id: id,
|
window_id: id,
|
||||||
|
@ -277,6 +354,8 @@ impl<T: 'static> Shared<T> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the destroy-pending windows again.
|
||||||
|
self.process_destroy_pending_windows(&mut control);
|
||||||
self.handle_event(Event::MainEventsCleared, &mut control);
|
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||||
|
|
||||||
// Discard all the pending redraw as we shall just redraw all windows.
|
// Discard all the pending redraw as we shall just redraw all windows.
|
||||||
|
@ -290,13 +369,15 @@ impl<T: 'static> Shared<T> {
|
||||||
// If the event loop is closed, it has been closed this iteration and now the closing
|
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||||
// event should be emitted
|
// event should be emitted
|
||||||
if self.is_closed() {
|
if self.is_closed() {
|
||||||
self.handle_event(Event::LoopDestroyed, &mut control);
|
self.handle_loop_destroyed(&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();
|
||||||
|
// We don't call `handle_loop_destroyed` here because we don't need to
|
||||||
|
// perform cleanup when the web browser is going to destroy the page.
|
||||||
self.handle_event(Event::LoopDestroyed, &mut control);
|
self.handle_event(Event::LoopDestroyed, &mut control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +389,7 @@ impl<T: 'static> Shared<T> {
|
||||||
*control = root::ControlFlow::Exit;
|
*control = root::ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
match *self.0.runner.borrow_mut() {
|
match *self.0.runner.borrow_mut() {
|
||||||
Some(ref mut runner) => {
|
RunnerEnum::Running(ref mut runner) => {
|
||||||
runner.handle_single_event(event, control);
|
runner.handle_single_event(event, control);
|
||||||
}
|
}
|
||||||
_ => panic!("Cannot handle event synchronously without a runner"),
|
_ => panic!("Cannot handle event synchronously without a runner"),
|
||||||
|
@ -323,19 +404,21 @@ impl<T: 'static> Shared<T> {
|
||||||
*control = root::ControlFlow::Exit;
|
*control = root::ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
match *self.0.runner.borrow_mut() {
|
match *self.0.runner.borrow_mut() {
|
||||||
Some(ref mut runner) => {
|
RunnerEnum::Running(ref mut runner) => {
|
||||||
runner.handle_single_event(event, control);
|
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),
|
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event),
|
||||||
|
// If the Runner has been destroyed, there is nothing to do.
|
||||||
|
RunnerEnum::Destroyed => return,
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_closed = *control == root::ControlFlow::Exit;
|
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().maybe_runner().is_some() {
|
||||||
// Take an event out of the queue and handle it
|
// Take an event out of the queue and handle it
|
||||||
// Make sure not to let the borrow_mut live during the next handle_event
|
// Make sure not to let the borrow_mut live during the next handle_event
|
||||||
let event = { self.0.events.borrow_mut().pop_front() };
|
let event = { self.0.events.borrow_mut().pop_front() };
|
||||||
|
@ -382,26 +465,58 @@ impl<T: 'static> Shared<T> {
|
||||||
};
|
};
|
||||||
|
|
||||||
match *self.0.runner.borrow_mut() {
|
match *self.0.runner.borrow_mut() {
|
||||||
Some(ref mut runner) => {
|
RunnerEnum::Running(ref mut runner) => {
|
||||||
runner.state = new_state;
|
runner.state = new_state;
|
||||||
}
|
}
|
||||||
None => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_loop_destroyed(&self, control: &mut root::ControlFlow) {
|
||||||
|
self.handle_event(Event::LoopDestroyed, control);
|
||||||
|
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
|
||||||
|
*self.0.scale_change_detector.borrow_mut() = None;
|
||||||
|
*self.0.unload_event_handle.borrow_mut() = None;
|
||||||
|
// Dropping the `Runner` drops the event handler closure, which will in
|
||||||
|
// turn drop all `Window`s moved into the closure.
|
||||||
|
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
||||||
|
for (_, canvas) in all_canvases {
|
||||||
|
// In case any remaining `Window`s are still not dropped, we will need
|
||||||
|
// to explicitly remove the event handlers associated with their canvases.
|
||||||
|
if let Some(canvas) = canvas.upgrade() {
|
||||||
|
let mut canvas = canvas.borrow_mut();
|
||||||
|
canvas.remove_listeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At this point, the `self.0` `Rc` should only be strongly referenced
|
||||||
|
// by the following:
|
||||||
|
// * `self`, i.e. the item which triggered this event loop wakeup, which
|
||||||
|
// is usually a `wasm-bindgen` `Closure`, which will be dropped after
|
||||||
|
// returning to the JS glue code.
|
||||||
|
// * The `EventLoopWindowTarget` leaked inside `EventLoop::run` due to the
|
||||||
|
// JS exception thrown at the end.
|
||||||
|
// * For each undropped `Window`:
|
||||||
|
// * The `register_redraw_request` closure.
|
||||||
|
// * The `destroy_fn` closure.
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the event loop is currently closed
|
// Check if the event loop is currently closed
|
||||||
fn is_closed(&self) -> bool {
|
fn is_closed(&self) -> bool {
|
||||||
match *self.0.runner.borrow() {
|
match *self.0.runner.borrow() {
|
||||||
Some(ref runner) => runner.state.is_exit(),
|
RunnerEnum::Running(ref runner) => runner.state.is_exit(),
|
||||||
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
|
// The event loop is not closed since it is not initialized.
|
||||||
|
RunnerEnum::Pending => false,
|
||||||
|
// The event loop is closed since it has been destroyed.
|
||||||
|
RunnerEnum::Destroyed => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current control flow state
|
// Get the current control flow state
|
||||||
fn current_control_flow(&self) -> root::ControlFlow {
|
fn current_control_flow(&self) -> root::ControlFlow {
|
||||||
match *self.0.runner.borrow() {
|
match *self.0.runner.borrow() {
|
||||||
Some(ref runner) => runner.state.control_flow(),
|
RunnerEnum::Running(ref runner) => runner.state.control_flow(),
|
||||||
None => root::ControlFlow::Poll,
|
RunnerEnum::Pending => root::ControlFlow::Poll,
|
||||||
|
RunnerEnum::Destroyed => root::ControlFlow::Exit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, Win
|
||||||
use crate::event_loop::ControlFlow;
|
use crate::event_loop::ControlFlow;
|
||||||
use crate::monitor::MonitorHandle as RootMH;
|
use crate::monitor::MonitorHandle as RootMH;
|
||||||
use crate::window::{Theme, WindowId};
|
use crate::window::{Theme, WindowId};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct WindowTarget<T: 'static> {
|
pub struct WindowTarget<T: 'static> {
|
||||||
pub(crate) runner: runner::Shared<T>,
|
pub(crate) runner: runner::Shared<T>,
|
||||||
|
@ -42,11 +44,12 @@ impl<T> WindowTarget<T> {
|
||||||
window::Id(self.runner.generate_id())
|
window::Id(self.runner.generate_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
|
pub fn register(&self, canvas: &Rc<RefCell<backend::Canvas>>, id: window::Id) {
|
||||||
let runner = self.runner.clone();
|
self.runner.add_canvas(WindowId(id), canvas);
|
||||||
|
let mut canvas = canvas.borrow_mut();
|
||||||
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());
|
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
canvas.on_blur(move || {
|
canvas.on_blur(move || {
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: WindowId(id),
|
window_id: WindowId(id),
|
||||||
|
|
|
@ -37,12 +37,6 @@ pub struct Canvas {
|
||||||
wants_fullscreen: Rc<RefCell<bool>>,
|
wants_fullscreen: Rc<RefCell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Canvas {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.raw.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||||
let canvas = match attr.canvas {
|
let canvas = match attr.canvas {
|
||||||
|
@ -306,4 +300,8 @@ impl Canvas {
|
||||||
pub fn is_fullscreen(&self) -> bool {
|
pub fn is_fullscreen(&self) -> bool {
|
||||||
super::is_fullscreen(&self.raw)
|
super::is_fullscreen(&self.raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_listeners(&mut self) {
|
||||||
|
// TODO: Stub, unimplemented (see web_sys for reference).
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@ pub fn exit_fullscreen() {
|
||||||
document().exit_fullscreen();
|
document().exit_fullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_unload(mut handler: impl FnMut() + 'static) {
|
pub type UnloadEventHandle = ();
|
||||||
|
|
||||||
|
pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle {
|
||||||
window().add_event_listener(move |_: BeforeUnloadEvent| handler());
|
window().add_event_listener(move |_: BeforeUnloadEvent| handler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use super::event;
|
use super::event;
|
||||||
|
use super::event_handle::EventListenerHandle;
|
||||||
|
use super::media_query_handle::MediaQueryListHandle;
|
||||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||||
use crate::error::OsError as RootOE;
|
use crate::error::OsError as RootOE;
|
||||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
@ -18,14 +20,14 @@ mod pointer_handler;
|
||||||
|
|
||||||
pub struct Canvas {
|
pub struct Canvas {
|
||||||
common: Common,
|
common: Common,
|
||||||
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
|
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
|
||||||
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
|
on_blur: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
|
||||||
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
on_keyboard_release: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
|
||||||
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
on_keyboard_press: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
|
||||||
on_received_character: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
on_received_character: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
|
||||||
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
|
on_mouse_wheel: Option<EventListenerHandle<dyn FnMut(WheelEvent)>>,
|
||||||
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
|
on_fullscreen_change: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||||
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
on_dark_mode: Option<MediaQueryListHandle>,
|
||||||
mouse_state: MouseState,
|
mouse_state: MouseState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,12 +37,6 @@ struct Common {
|
||||||
wants_fullscreen: Rc<RefCell<bool>>,
|
wants_fullscreen: Rc<RefCell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Common {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.raw.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||||
let canvas = match attr.canvas {
|
let canvas = match attr.canvas {
|
||||||
|
@ -264,22 +260,12 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(bool),
|
F: 'static + FnMut(bool),
|
||||||
{
|
{
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
let closure =
|
||||||
|
Closure::wrap(
|
||||||
self.on_dark_mode = window
|
Box::new(move |event: MediaQueryListEvent| handler(event.matches()))
|
||||||
.match_media("(prefers-color-scheme: dark)")
|
as Box<dyn FnMut(_)>,
|
||||||
.ok()
|
);
|
||||||
.flatten()
|
self.on_dark_mode = MediaQueryListHandle::new("(prefers-color-scheme: dark)", closure);
|
||||||
.and_then(|media| {
|
|
||||||
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
|
|
||||||
handler(event.matches())
|
|
||||||
}) as Box<dyn FnMut(_)>);
|
|
||||||
|
|
||||||
media
|
|
||||||
.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref()))
|
|
||||||
.map(|_| closure)
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_fullscreen(&self) {
|
pub fn request_fullscreen(&self) {
|
||||||
|
@ -289,10 +275,29 @@ impl Canvas {
|
||||||
pub fn is_fullscreen(&self) -> bool {
|
pub fn is_fullscreen(&self) -> bool {
|
||||||
self.common.is_fullscreen()
|
self.common.is_fullscreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_listeners(&mut self) {
|
||||||
|
self.on_focus = None;
|
||||||
|
self.on_blur = None;
|
||||||
|
self.on_keyboard_release = None;
|
||||||
|
self.on_keyboard_press = None;
|
||||||
|
self.on_received_character = None;
|
||||||
|
self.on_mouse_wheel = None;
|
||||||
|
self.on_fullscreen_change = None;
|
||||||
|
self.on_dark_mode = None;
|
||||||
|
match &mut self.mouse_state {
|
||||||
|
MouseState::HasPointerEvent(h) => h.remove_listeners(),
|
||||||
|
MouseState::NoPointerEvent(h) => h.remove_listeners(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Common {
|
impl Common {
|
||||||
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
fn add_event<E, F>(
|
||||||
|
&self,
|
||||||
|
event_name: &'static str,
|
||||||
|
mut handler: F,
|
||||||
|
) -> EventListenerHandle<dyn FnMut(E)>
|
||||||
where
|
where
|
||||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||||
F: 'static + FnMut(E),
|
F: 'static + FnMut(E),
|
||||||
|
@ -307,17 +312,19 @@ impl Common {
|
||||||
handler(event);
|
handler(event);
|
||||||
}) as Box<dyn FnMut(E)>);
|
}) as Box<dyn FnMut(E)>);
|
||||||
|
|
||||||
self.raw
|
let listener = EventListenerHandle::new(&self.raw, event_name, closure);
|
||||||
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
|
|
||||||
.expect("Failed to add event listener with callback");
|
|
||||||
|
|
||||||
closure
|
listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// The difference between add_event and add_user_event is that the latter has a special meaning
|
// The difference between add_event and add_user_event is that the latter has a special meaning
|
||||||
// for browser security. A user event is a deliberate action by the user (like a mouse or key
|
// for browser security. A user event is a deliberate action by the user (like a mouse or key
|
||||||
// press) and is the only time things like a fullscreen request may be successfully completed.)
|
// press) and is the only time things like a fullscreen request may be successfully completed.)
|
||||||
fn add_user_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
fn add_user_event<E, F>(
|
||||||
|
&self,
|
||||||
|
event_name: &'static str,
|
||||||
|
mut handler: F,
|
||||||
|
) -> EventListenerHandle<dyn FnMut(E)>
|
||||||
where
|
where
|
||||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||||
F: 'static + FnMut(E),
|
F: 'static + FnMut(E),
|
||||||
|
@ -343,9 +350,9 @@ impl Common {
|
||||||
// handling to control event propagation.
|
// handling to control event propagation.
|
||||||
fn add_window_mouse_event<F>(
|
fn add_window_mouse_event<F>(
|
||||||
&self,
|
&self,
|
||||||
event_name: &str,
|
event_name: &'static str,
|
||||||
mut handler: F,
|
mut handler: F,
|
||||||
) -> Closure<dyn FnMut(MouseEvent)>
|
) -> EventListenerHandle<dyn FnMut(MouseEvent)>
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(MouseEvent),
|
F: 'static + FnMut(MouseEvent),
|
||||||
{
|
{
|
||||||
|
@ -364,15 +371,14 @@ impl Common {
|
||||||
}
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
|
||||||
window
|
let listener = EventListenerHandle::with_options(
|
||||||
.add_event_listener_with_callback_and_add_event_listener_options(
|
&window,
|
||||||
event_name,
|
event_name,
|
||||||
&closure.as_ref().unchecked_ref(),
|
closure,
|
||||||
AddEventListenerOptions::new().capture(true),
|
AddEventListenerOptions::new().capture(true),
|
||||||
)
|
);
|
||||||
.expect("Failed to add event listener with callback and options");
|
|
||||||
|
|
||||||
closure
|
listener
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_fullscreen(&self) {
|
pub fn request_fullscreen(&self) {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use super::event;
|
use super::event;
|
||||||
|
use super::EventListenerHandle;
|
||||||
use crate::dpi::PhysicalPosition;
|
use crate::dpi::PhysicalPosition;
|
||||||
use crate::event::{ModifiersState, MouseButton};
|
use crate::event::{ModifiersState, MouseButton};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use wasm_bindgen::closure::Closure;
|
|
||||||
use web_sys::{EventTarget, MouseEvent};
|
use web_sys::{EventTarget, MouseEvent};
|
||||||
|
|
||||||
pub(super) struct MouseHandler {
|
pub(super) struct MouseHandler {
|
||||||
on_mouse_leave: Option<Closure<dyn FnMut(MouseEvent)>>,
|
on_mouse_leave: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||||
on_mouse_enter: Option<Closure<dyn FnMut(MouseEvent)>>,
|
on_mouse_enter: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||||
on_mouse_move: Option<Closure<dyn FnMut(MouseEvent)>>,
|
on_mouse_move: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||||
on_mouse_press: Option<Closure<dyn FnMut(MouseEvent)>>,
|
on_mouse_press: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||||
on_mouse_release: Option<Closure<dyn FnMut(MouseEvent)>>,
|
on_mouse_release: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||||
on_mouse_leave_handler: Rc<RefCell<Option<Box<dyn FnMut(i32)>>>>,
|
on_mouse_leave_handler: Rc<RefCell<Option<Box<dyn FnMut(i32)>>>>,
|
||||||
mouse_capture_state: Rc<RefCell<MouseCaptureState>>,
|
mouse_capture_state: Rc<RefCell<MouseCaptureState>>,
|
||||||
}
|
}
|
||||||
|
@ -200,4 +200,13 @@ impl MouseHandler {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_listeners(&mut self) {
|
||||||
|
self.on_mouse_leave = None;
|
||||||
|
self.on_mouse_enter = None;
|
||||||
|
self.on_mouse_move = None;
|
||||||
|
self.on_mouse_press = None;
|
||||||
|
self.on_mouse_release = None;
|
||||||
|
*self.on_mouse_leave_handler.borrow_mut() = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use super::event;
|
use super::event;
|
||||||
|
use super::EventListenerHandle;
|
||||||
use crate::dpi::PhysicalPosition;
|
use crate::dpi::PhysicalPosition;
|
||||||
use crate::event::{ModifiersState, MouseButton};
|
use crate::event::{ModifiersState, MouseButton};
|
||||||
|
|
||||||
use wasm_bindgen::closure::Closure;
|
|
||||||
use web_sys::PointerEvent;
|
use web_sys::PointerEvent;
|
||||||
|
|
||||||
pub(super) struct PointerHandler {
|
pub(super) struct PointerHandler {
|
||||||
on_cursor_leave: Option<Closure<dyn FnMut(PointerEvent)>>,
|
on_cursor_leave: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
on_cursor_enter: Option<Closure<dyn FnMut(PointerEvent)>>,
|
on_cursor_enter: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
on_cursor_move: Option<Closure<dyn FnMut(PointerEvent)>>,
|
on_cursor_move: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
on_pointer_press: Option<Closure<dyn FnMut(PointerEvent)>>,
|
on_pointer_press: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
on_pointer_release: Option<Closure<dyn FnMut(PointerEvent)>>,
|
on_pointer_release: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerHandler {
|
impl PointerHandler {
|
||||||
|
@ -100,4 +100,12 @@ impl PointerHandler {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_listeners(&mut self) {
|
||||||
|
self.on_cursor_leave = None;
|
||||||
|
self.on_cursor_enter = None;
|
||||||
|
self.on_cursor_move = None;
|
||||||
|
self.on_pointer_press = None;
|
||||||
|
self.on_pointer_release = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
65
src/platform_impl/web/web_sys/event_handle.rs
Normal file
65
src/platform_impl/web/web_sys/event_handle.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||||
|
use web_sys::{AddEventListenerOptions, EventTarget};
|
||||||
|
|
||||||
|
pub(super) struct EventListenerHandle<T: ?Sized> {
|
||||||
|
target: EventTarget,
|
||||||
|
event_type: &'static str,
|
||||||
|
listener: Closure<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> EventListenerHandle<T> {
|
||||||
|
pub fn new<U>(target: &U, event_type: &'static str, listener: Closure<T>) -> Self
|
||||||
|
where
|
||||||
|
U: Clone + Into<EventTarget>,
|
||||||
|
{
|
||||||
|
let target = target.clone().into();
|
||||||
|
target
|
||||||
|
.add_event_listener_with_callback(event_type, listener.as_ref().unchecked_ref())
|
||||||
|
.expect("Failed to add event listener");
|
||||||
|
EventListenerHandle {
|
||||||
|
target,
|
||||||
|
event_type,
|
||||||
|
listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_options<U>(
|
||||||
|
target: &U,
|
||||||
|
event_type: &'static str,
|
||||||
|
listener: Closure<T>,
|
||||||
|
options: &AddEventListenerOptions,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
U: Clone + Into<EventTarget>,
|
||||||
|
{
|
||||||
|
let target = target.clone().into();
|
||||||
|
target
|
||||||
|
.add_event_listener_with_callback_and_add_event_listener_options(
|
||||||
|
event_type,
|
||||||
|
listener.as_ref().unchecked_ref(),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.expect("Failed to add event listener");
|
||||||
|
EventListenerHandle {
|
||||||
|
target,
|
||||||
|
event_type,
|
||||||
|
listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Drop for EventListenerHandle<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.target
|
||||||
|
.remove_event_listener_with_callback(
|
||||||
|
self.event_type,
|
||||||
|
self.listener.as_ref().unchecked_ref(),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
web_sys::console::error_2(
|
||||||
|
&format!("Error removing event listener {}", self.event_type).into(),
|
||||||
|
&e,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
56
src/platform_impl/web/web_sys/media_query_handle.rs
Normal file
56
src/platform_impl/web/web_sys/media_query_handle.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||||
|
use web_sys::{MediaQueryList, MediaQueryListEvent};
|
||||||
|
|
||||||
|
pub(super) struct MediaQueryListHandle {
|
||||||
|
mql: MediaQueryList,
|
||||||
|
listener: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaQueryListHandle {
|
||||||
|
pub fn new(
|
||||||
|
media_query: &str,
|
||||||
|
listener: Closure<dyn FnMut(MediaQueryListEvent)>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
let mql = window
|
||||||
|
.match_media(media_query)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|mql| {
|
||||||
|
mql.add_listener_with_opt_callback(Some(&listener.as_ref().unchecked_ref()))
|
||||||
|
.map(|_| mql)
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
mql.map(|mql| Self {
|
||||||
|
mql,
|
||||||
|
listener: Some(listener),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mql(&self) -> &MediaQueryList {
|
||||||
|
&self.mql
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the listener and returns the original listener closure, which
|
||||||
|
/// can be reused.
|
||||||
|
pub fn remove(mut self) -> Closure<dyn FnMut(MediaQueryListEvent)> {
|
||||||
|
let listener = self.listener.take().unwrap_or_else(|| unreachable!());
|
||||||
|
remove_listener(&self.mql, &listener);
|
||||||
|
listener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MediaQueryListHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(listener) = self.listener.take() {
|
||||||
|
remove_listener(&self.mql, &listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_listener(mql: &MediaQueryList, listener: &Closure<dyn FnMut(MediaQueryListEvent)>) {
|
||||||
|
mql.remove_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref()))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
web_sys::console::error_2(&"Error removing media query listener".into(), &e)
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod event_handle;
|
||||||
|
mod media_query_handle;
|
||||||
mod scaling;
|
mod scaling;
|
||||||
mod timeout;
|
mod timeout;
|
||||||
|
|
||||||
|
@ -10,7 +12,7 @@ pub use self::timeout::{AnimationFrameRequest, Timeout};
|
||||||
use crate::dpi::{LogicalSize, Size};
|
use crate::dpi::{LogicalSize, Size};
|
||||||
use crate::platform::web::WindowExtWebSys;
|
use crate::platform::web::WindowExtWebSys;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::closure::Closure;
|
||||||
use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement};
|
use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement};
|
||||||
|
|
||||||
pub fn throw(msg: &str) {
|
pub fn throw(msg: &str) {
|
||||||
|
@ -24,16 +26,21 @@ pub fn exit_fullscreen() {
|
||||||
document.exit_fullscreen();
|
document.exit_fullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_unload(mut handler: impl FnMut() + 'static) {
|
pub struct UnloadEventHandle {
|
||||||
|
_listener: event_handle::EventListenerHandle<dyn FnMut(BeforeUnloadEvent)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
|
||||||
let closure = Closure::wrap(
|
let closure = Closure::wrap(
|
||||||
Box::new(move |_: BeforeUnloadEvent| handler()) as Box<dyn FnMut(BeforeUnloadEvent)>
|
Box::new(move |_: BeforeUnloadEvent| handler()) as Box<dyn FnMut(BeforeUnloadEvent)>
|
||||||
);
|
);
|
||||||
|
|
||||||
window
|
let listener = event_handle::EventListenerHandle::new(&window, "beforeunload", closure);
|
||||||
.add_event_listener_with_callback("beforeunload", &closure.as_ref().unchecked_ref())
|
UnloadEventHandle {
|
||||||
.expect("Failed to add close listener");
|
_listener: listener,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtWebSys for Window {
|
impl WindowExtWebSys for Window {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use super::super::ScaleChangeArgs;
|
use super::super::ScaleChangeArgs;
|
||||||
|
use super::media_query_handle::MediaQueryListHandle;
|
||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
use wasm_bindgen::prelude::Closure;
|
||||||
use web_sys::{MediaQueryList, MediaQueryListEvent};
|
use web_sys::MediaQueryListEvent;
|
||||||
|
|
||||||
pub struct ScaleChangeDetector(Rc<RefCell<ScaleChangeDetectorInternal>>);
|
pub struct ScaleChangeDetector(Rc<RefCell<ScaleChangeDetectorInternal>>);
|
||||||
|
|
||||||
|
@ -19,8 +20,7 @@ impl ScaleChangeDetector {
|
||||||
/// changes of the `devicePixelRatio`.
|
/// changes of the `devicePixelRatio`.
|
||||||
struct ScaleChangeDetectorInternal {
|
struct ScaleChangeDetectorInternal {
|
||||||
callback: Box<dyn FnMut(ScaleChangeArgs)>,
|
callback: Box<dyn FnMut(ScaleChangeArgs)>,
|
||||||
closure: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
mql: Option<MediaQueryListHandle>,
|
||||||
mql: Option<MediaQueryList>,
|
|
||||||
last_scale: f64,
|
last_scale: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,27 +32,28 @@ impl ScaleChangeDetectorInternal {
|
||||||
let current_scale = super::scale_factor();
|
let current_scale = super::scale_factor();
|
||||||
let new_self = Rc::new(RefCell::new(Self {
|
let new_self = Rc::new(RefCell::new(Self {
|
||||||
callback: Box::new(handler),
|
callback: Box::new(handler),
|
||||||
closure: None,
|
|
||||||
mql: None,
|
mql: None,
|
||||||
last_scale: current_scale,
|
last_scale: current_scale,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let cloned_self = new_self.clone();
|
let weak_self = Rc::downgrade(&new_self);
|
||||||
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
|
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
|
||||||
cloned_self.borrow_mut().handler(event)
|
if let Some(rc_self) = weak_self.upgrade() {
|
||||||
|
rc_self.borrow_mut().handler(event);
|
||||||
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
|
||||||
let mql = Self::create_mql(&closure);
|
let mql = Self::create_mql(closure);
|
||||||
{
|
{
|
||||||
let mut borrowed_self = new_self.borrow_mut();
|
let mut borrowed_self = new_self.borrow_mut();
|
||||||
borrowed_self.closure = Some(closure);
|
|
||||||
borrowed_self.mql = mql;
|
borrowed_self.mql = mql;
|
||||||
}
|
}
|
||||||
new_self
|
new_self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_mql(closure: &Closure<dyn FnMut(MediaQueryListEvent)>) -> Option<MediaQueryList> {
|
fn create_mql(
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
closure: Closure<dyn FnMut(MediaQueryListEvent)>,
|
||||||
|
) -> Option<MediaQueryListHandle> {
|
||||||
let current_scale = super::scale_factor();
|
let current_scale = super::scale_factor();
|
||||||
// This media query initially matches the current `devicePixelRatio`.
|
// This media query initially matches the current `devicePixelRatio`.
|
||||||
// We add 0.0001 to the lower and upper bounds such that it won't fail
|
// We add 0.0001 to the lower and upper bounds such that it won't fail
|
||||||
|
@ -62,30 +63,20 @@ impl ScaleChangeDetectorInternal {
|
||||||
current_scale - 0.0001,
|
current_scale - 0.0001,
|
||||||
current_scale + 0.0001,
|
current_scale + 0.0001,
|
||||||
);
|
);
|
||||||
window
|
let mql = MediaQueryListHandle::new(&media_query, closure);
|
||||||
.match_media(&media_query)
|
if let Some(mql) = &mql {
|
||||||
.ok()
|
assert_eq!(mql.mql().matches(), true);
|
||||||
.flatten()
|
}
|
||||||
.and_then(|mql| {
|
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) {
|
fn handler(&mut self, event: MediaQueryListEvent) {
|
||||||
assert_eq!(event.matches(), false);
|
assert_eq!(event.matches(), false);
|
||||||
let closure = self
|
|
||||||
.closure
|
|
||||||
.as_ref()
|
|
||||||
.expect("DevicePixelRatioChangeDetector::closure should not be None");
|
|
||||||
let mql = self
|
let mql = self
|
||||||
.mql
|
.mql
|
||||||
.take()
|
.take()
|
||||||
.expect("DevicePixelRatioChangeDetector::mql should not be None");
|
.expect("DevicePixelRatioChangeDetector::mql should not be None");
|
||||||
mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref()))
|
let closure = mql.remove();
|
||||||
.expect("Failed to remove listener from MediaQueryList");
|
|
||||||
let new_scale = super::scale_factor();
|
let new_scale = super::scale_factor();
|
||||||
(self.callback)(ScaleChangeArgs {
|
(self.callback)(ScaleChangeArgs {
|
||||||
old_scale: self.last_scale,
|
old_scale: self.last_scale,
|
||||||
|
@ -96,15 +87,3 @@ impl ScaleChangeDetectorInternal {
|
||||||
self.last_scale = new_scale;
|
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()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,15 +8,17 @@ use raw_window_handle::web::WebHandle;
|
||||||
|
|
||||||
use super::{backend, monitor, EventLoopWindowTarget};
|
use super::{backend, monitor, EventLoopWindowTarget};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::{Ref, RefCell};
|
||||||
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
canvas: backend::Canvas,
|
canvas: Rc<RefCell<backend::Canvas>>,
|
||||||
previous_pointer: RefCell<&'static str>,
|
previous_pointer: RefCell<&'static str>,
|
||||||
id: Id,
|
id: Id,
|
||||||
register_redraw_request: Box<dyn Fn()>,
|
register_redraw_request: Box<dyn Fn()>,
|
||||||
|
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -29,17 +31,22 @@ impl Window {
|
||||||
|
|
||||||
let id = target.generate_id();
|
let id = target.generate_id();
|
||||||
|
|
||||||
let mut canvas = backend::Canvas::create(platform_attr)?;
|
let canvas = backend::Canvas::create(platform_attr)?;
|
||||||
|
let mut canvas = Rc::new(RefCell::new(canvas));
|
||||||
|
|
||||||
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
||||||
|
|
||||||
target.register(&mut canvas, id);
|
target.register(&mut canvas, id);
|
||||||
|
|
||||||
|
let runner = target.runner.clone();
|
||||||
|
let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id)));
|
||||||
|
|
||||||
let window = Window {
|
let window = Window {
|
||||||
canvas,
|
canvas,
|
||||||
previous_pointer: RefCell::new("auto"),
|
previous_pointer: RefCell::new("auto"),
|
||||||
id,
|
id,
|
||||||
register_redraw_request,
|
register_redraw_request,
|
||||||
|
destroy_fn: Some(destroy_fn),
|
||||||
};
|
};
|
||||||
|
|
||||||
window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize {
|
window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize {
|
||||||
|
@ -54,12 +61,12 @@ impl Window {
|
||||||
Ok(window)
|
Ok(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canvas(&self) -> &backend::Canvas {
|
pub fn canvas<'a>(&'a self) -> Ref<'a, backend::Canvas> {
|
||||||
&self.canvas
|
self.canvas.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
pub fn set_title(&self, title: &str) {
|
||||||
self.canvas.set_attribute("alt", title);
|
self.canvas.borrow().set_attribute("alt", title);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_visible(&self, _visible: bool) {
|
pub fn set_visible(&self, _visible: bool) {
|
||||||
|
@ -71,7 +78,11 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||||
Ok(self.canvas.position().to_physical(self.scale_factor()))
|
Ok(self
|
||||||
|
.canvas
|
||||||
|
.borrow()
|
||||||
|
.position()
|
||||||
|
.to_physical(self.scale_factor()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||||
|
@ -82,14 +93,15 @@ impl Window {
|
||||||
pub fn set_outer_position(&self, position: Position) {
|
pub fn set_outer_position(&self, position: Position) {
|
||||||
let position = position.to_logical::<f64>(self.scale_factor());
|
let position = position.to_logical::<f64>(self.scale_factor());
|
||||||
|
|
||||||
self.canvas.set_attribute("position", "fixed");
|
let canvas = self.canvas.borrow();
|
||||||
self.canvas.set_attribute("left", &position.x.to_string());
|
canvas.set_attribute("position", "fixed");
|
||||||
self.canvas.set_attribute("top", &position.y.to_string());
|
canvas.set_attribute("left", &position.x.to_string());
|
||||||
|
canvas.set_attribute("top", &position.y.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||||
self.canvas.size()
|
self.canvas.borrow().size()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -100,7 +112,7 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
pub fn set_inner_size(&self, size: Size) {
|
||||||
backend::set_canvas_size(self.canvas.raw(), size);
|
backend::set_canvas_size(self.canvas.borrow().raw(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -165,7 +177,7 @@ impl Window {
|
||||||
CursorIcon::RowResize => "row-resize",
|
CursorIcon::RowResize => "row-resize",
|
||||||
};
|
};
|
||||||
*self.previous_pointer.borrow_mut() = text;
|
*self.previous_pointer.borrow_mut() = text;
|
||||||
backend::set_canvas_style_property(self.canvas.raw(), "cursor", text);
|
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -181,9 +193,10 @@ impl Window {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
pub fn set_cursor_visible(&self, visible: bool) {
|
||||||
if !visible {
|
if !visible {
|
||||||
self.canvas.set_attribute("cursor", "none");
|
self.canvas.borrow().set_attribute("cursor", "none");
|
||||||
} else {
|
} else {
|
||||||
self.canvas
|
self.canvas
|
||||||
|
.borrow()
|
||||||
.set_attribute("cursor", *self.previous_pointer.borrow());
|
.set_attribute("cursor", *self.previous_pointer.borrow());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +213,7 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
if self.canvas.is_fullscreen() {
|
if self.canvas.borrow().is_fullscreen() {
|
||||||
Some(Fullscreen::Borderless(self.current_monitor_inner()))
|
Some(Fullscreen::Borderless(self.current_monitor_inner()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -210,8 +223,8 @@ impl Window {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||||
if monitor.is_some() {
|
if monitor.is_some() {
|
||||||
self.canvas.request_fullscreen();
|
self.canvas.borrow().request_fullscreen();
|
||||||
} else if self.canvas.is_fullscreen() {
|
} else if self.canvas.borrow().is_fullscreen() {
|
||||||
backend::exit_fullscreen();
|
backend::exit_fullscreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,6 +290,14 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Window {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(destroy_fn) = self.destroy_fn.take() {
|
||||||
|
destroy_fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Id(pub(crate) u32);
|
pub struct Id(pub(crate) u32);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue