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
12 changed files with 302 additions and 44 deletions
|
|
@ -28,8 +28,7 @@ impl<T> EventLoop<T> {
|
|||
|
||||
pub fn run<F>(self, mut event_handler: F) -> !
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(Event<'static, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
|
||||
F: 'static + FnMut(Event<'_, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
|
||||
{
|
||||
let target = root::EventLoopWindowTarget {
|
||||
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_loop as root;
|
||||
use crate::window::WindowId;
|
||||
|
|
@ -24,23 +24,60 @@ pub struct Execution<T: 'static> {
|
|||
runner: RefCell<Option<Runner<T>>>,
|
||||
events: RefCell<VecDeque<Event<'static, T>>>,
|
||||
id: RefCell<u32>,
|
||||
all_canvases: RefCell<Vec<(WindowId, backend::RawCanvasType)>>,
|
||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
|
||||
}
|
||||
|
||||
struct Runner<T: 'static> {
|
||||
state: State,
|
||||
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> {
|
||||
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 {
|
||||
state: State::Init,
|
||||
is_busy: false,
|
||||
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> {
|
||||
|
|
@ -49,16 +86,22 @@ impl<T: 'static> Shared<T> {
|
|||
runner: RefCell::new(None),
|
||||
events: RefCell::new(VecDeque::new()),
|
||||
id: RefCell::new(0),
|
||||
all_canvases: RefCell::new(Vec::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
|
||||
// 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<'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.init();
|
||||
|
|
@ -67,6 +110,14 @@ impl<T: 'static> Shared<T> {
|
|||
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
|
||||
// This is used to differentiate windows when handling events
|
||||
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
|
||||
// Now we determine why new events are incoming, and handle the events
|
||||
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
|
||||
match runner.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 => {
|
||||
// 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");
|
||||
let start_cause = match (self.0.runner.borrow().as_ref())
|
||||
.unwrap_or_else(|| {
|
||||
unreachable!("The runner cannot process events when it is not attached")
|
||||
})
|
||||
.maybe_start_cause()
|
||||
{
|
||||
Some(c) => c,
|
||||
// If we're in the exit state, don't do event processing
|
||||
None => return,
|
||||
};
|
||||
// Take the start event, then the events provided to this function, and run an iteration of
|
||||
// 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) {
|
||||
self.apply_control_flow(root::ControlFlow::Exit);
|
||||
let mut control = self.current_control_flow();
|
||||
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
|
||||
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
|
||||
let is_closed = self.is_closed();
|
||||
// It should only ever be called from `scale_changed`.
|
||||
fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut root::ControlFlow) {
|
||||
if self.is_closed() {
|
||||
*control = root::ControlFlow::Exit;
|
||||
}
|
||||
match *self.0.runner.borrow_mut() {
|
||||
Some(ref mut runner) => {
|
||||
// An event is being processed, so the runner should be marked busy
|
||||
runner.is_busy = true;
|
||||
runner.handle_single_event(event, control);
|
||||
}
|
||||
_ => panic!("Cannot handle event synchronously without a runner"),
|
||||
}
|
||||
}
|
||||
|
||||
(runner.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
|
||||
runner.is_busy = false;
|
||||
// handle_event takes in events and either queues them or applies a callback
|
||||
//
|
||||
// It should only ever be called from `run_until_cleared` and `scale_changed`.
|
||||
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
|
||||
if self.is_closed() {
|
||||
*control = root::ControlFlow::Exit;
|
||||
}
|
||||
match *self.0.runner.borrow_mut() {
|
||||
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
|
||||
// it will eventually be processed
|
||||
_ => 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
|
||||
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
||||
if !is_closed && self.0.runner.borrow().is_some() {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,12 @@ impl<T> WindowTarget<T> {
|
|||
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);
|
||||
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 {
|
||||
|
|
@ -40,6 +44,7 @@ impl<T> WindowTarget<T> {
|
|||
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
|
||||
let runner = self.runner.clone();
|
||||
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
||||
runner.add_canvas(WindowId(id), canvas.raw().clone());
|
||||
|
||||
canvas.on_blur(move || {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue