Replace Closed event with CloseRequested and Destroyed (#476)
* Replace Closed event with CloseRequested and Destroyed Implements #434 The existing Closed event had ambiguous meaning, both in name and in cross-platform behavior. Closed is now split into two more precise events: * CloseRequested - the window has been requested to close, most commonly by having clicked the window's close button. Whether or not you respond by closing the window is up to you. * Destroyed - the window has been destroyed, and can no longer be safely used. Most notably, now you can reliably implement classic patterns like prompting the user to save their work before closing, and have the opportunity to perform any necessary cleanup. Migrating to the new API is straightforward. In most cases, you can simply replace all existing usages of Closed with CloseRequested. For more information, see the example programs, particularly handling_close and multiwindow. iOS applications must replace all usages of Closed with Destroyed, and require no other changes.
This commit is contained in:
parent
42f0671531
commit
eadd9a19b2
21 changed files with 213 additions and 116 deletions
|
|
@ -51,12 +51,12 @@
|
|||
//! - applicationWillResignActive is Focused(false)
|
||||
//! - applicationDidEnterBackground is Suspended(true)
|
||||
//! - applicationWillEnterForeground is Suspended(false)
|
||||
//! - applicationWillTerminate is Closed
|
||||
//! - applicationWillTerminate is Destroyed
|
||||
//!
|
||||
//! Keep in mind that after Closed event is received every attempt to draw with
|
||||
//! Keep in mind that after Destroyed event is received every attempt to draw with
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app will not receive Closed event if suspended, it will be SIGKILL'ed
|
||||
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
|
|
@ -459,7 +459,7 @@ fn create_delegate_class() {
|
|||
// immidiatly after jump
|
||||
state.events_queue.push_front(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Closed,
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
longjmp(mem::transmute(&mut jmpbuf),1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -335,8 +335,11 @@ impl EventsLoop {
|
|||
{
|
||||
let mut cleanup_needed = self.cleanup_needed.lock().unwrap();
|
||||
if *cleanup_needed {
|
||||
evq.state().get_mut(&self.store).cleanup();
|
||||
let pruned = evq.state().get_mut(&self.store).cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_event(::WindowEvent::Destroyed, wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
// process pending resize/refresh
|
||||
|
|
@ -355,7 +358,7 @@ impl EventsLoop {
|
|||
sink.send_event(::WindowEvent::Refresh, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(::WindowEvent::Closed, wid);
|
||||
sink.send_event(::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -262,16 +262,19 @@ impl WindowStore {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) {
|
||||
pub fn cleanup(&mut self) -> Vec<WindowId> {
|
||||
let mut pruned = Vec::new();
|
||||
self.windows.retain(|w| {
|
||||
if *w.kill_switch.lock().unwrap() {
|
||||
// window is dead, cleanup
|
||||
pruned.push(make_wid(&w.surface));
|
||||
w.surface.destroy();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
pruned
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&mut self, mut f: F)
|
||||
|
|
|
|||
|
|
@ -245,15 +245,7 @@ impl EventsLoop {
|
|||
let window_id = mkwid(window);
|
||||
|
||||
if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window {
|
||||
callback(Event::WindowEvent { window_id, event: WindowEvent::Closed });
|
||||
|
||||
if let Some(_) = self.windows.lock().unwrap().remove(&WindowId(window)) {
|
||||
unsafe {
|
||||
(self.display.xlib.XDestroyWindow)(self.display.display, window);
|
||||
}
|
||||
self.display.check_errors()
|
||||
.expect("Failed to destroy window");
|
||||
}
|
||||
callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested });
|
||||
} else if client_msg.message_type == self.dnd.atoms.enter {
|
||||
let source_window = client_msg.data.get_long(0) as c_ulong;
|
||||
let flags = client_msg.data.get_long(1);
|
||||
|
|
@ -430,11 +422,20 @@ impl EventsLoop {
|
|||
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
|
||||
|
||||
let window = xev.window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
// In the event that the window's been destroyed without being dropped first, we
|
||||
// cleanup again here.
|
||||
self.windows.lock().unwrap().remove(&WindowId(window));
|
||||
|
||||
// Since all XIM stuff needs to happen from the same thread, we destroy the input
|
||||
// context here instead of when dropping the window.
|
||||
self.ime
|
||||
.borrow_mut()
|
||||
.remove_context(window)
|
||||
.expect("Failed to destroy input context");
|
||||
|
||||
callback(Event::WindowEvent { window_id, event: WindowEvent::Destroyed });
|
||||
}
|
||||
|
||||
ffi::Expose => {
|
||||
|
|
@ -1059,27 +1060,12 @@ impl Window {
|
|||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
|
||||
// It's possible for the Window object to outlive the actual window, so we need to
|
||||
// check for that, lest the program explode with BadWindow errors soon after this.
|
||||
let window_closed = windows
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.window.id())
|
||||
.is_none();
|
||||
if !window_closed { unsafe {
|
||||
let wm_protocols_atom = util::get_atom(&display, b"WM_PROTOCOLS\0")
|
||||
.expect("Failed to call XInternAtom (WM_PROTOCOLS)");
|
||||
let wm_delete_atom = util::get_atom(&display, b"WM_DELETE_WINDOW\0")
|
||||
.expect("Failed to call XInternAtom (WM_DELETE_WINDOW)");
|
||||
util::send_client_msg(
|
||||
&display,
|
||||
self.window.id().0,
|
||||
self.window.id().0,
|
||||
wm_protocols_atom,
|
||||
None,
|
||||
(wm_delete_atom as _, ffi::CurrentTime as _, 0, 0, 0),
|
||||
).expect("Failed to send window deletion message");
|
||||
} }
|
||||
if let Some(_) = windows.lock().unwrap().remove(&self.window.id()) {
|
||||
unsafe {
|
||||
(display.xlib.XDestroyWindow)(display.display, self.window.id().0);
|
||||
display.check_errors().expect("Failed to destroy window");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ impl Shared {
|
|||
|
||||
// Removes the window with the given `Id` from the `windows` list.
|
||||
//
|
||||
// This is called when a window is either `Closed` or `Drop`ped.
|
||||
// This is called in response to `windowWillClose`.
|
||||
pub fn find_and_remove_window(&self, id: super::window::Id) {
|
||||
if let Ok(mut windows) = self.windows.lock() {
|
||||
windows.retain(|w| match w.upgrade() {
|
||||
|
|
|
|||
|
|
@ -165,7 +165,17 @@ impl WindowDelegate {
|
|||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
emit_event(state, WindowEvent::Closed);
|
||||
emit_event(state, WindowEvent::CloseRequested);
|
||||
}
|
||||
NO
|
||||
}
|
||||
|
||||
extern fn window_will_close(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
|
||||
emit_event(state, WindowEvent::Destroyed);
|
||||
|
||||
// Remove the window from the shared state.
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
|
|
@ -173,7 +183,6 @@ impl WindowDelegate {
|
|||
shared.find_and_remove_window(window_id);
|
||||
}
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern fn window_did_resize(this: &Object, _: Sel, _: id) {
|
||||
|
|
@ -294,7 +303,7 @@ impl WindowDelegate {
|
|||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.win_attribs.borrow_mut().fullscreen = Some(get_current_monitor());
|
||||
|
||||
|
||||
state.handle_with_fullscreen = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -323,7 +332,7 @@ impl WindowDelegate {
|
|||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
|
|
@ -342,10 +351,10 @@ impl WindowDelegate {
|
|||
let state = &mut *(state as *mut DelegateState);
|
||||
|
||||
if state.handle_with_fullscreen {
|
||||
let _: () = msg_send![*state.window,
|
||||
let _: () = msg_send![*state.window,
|
||||
performSelector:sel!(toggleFullScreen:)
|
||||
withObject:nil
|
||||
afterDelay: 0.5
|
||||
afterDelay: 0.5
|
||||
];
|
||||
} else {
|
||||
state.restore_state_from_fullscreen();
|
||||
|
|
@ -364,6 +373,8 @@ impl WindowDelegate {
|
|||
// Add callback methods
|
||||
decl.add_method(sel!(windowShouldClose:),
|
||||
window_should_close as extern fn(&Object, Sel, id) -> BOOL);
|
||||
decl.add_method(sel!(windowWillClose:),
|
||||
window_will_close as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowDidResize:),
|
||||
window_did_resize as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowDidChangeScreen:),
|
||||
|
|
@ -396,7 +407,7 @@ impl WindowDelegate {
|
|||
window_did_exit_fullscreen as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowDidFailToEnterFullScreen:),
|
||||
window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id));
|
||||
|
||||
|
||||
// Store internal state as user data
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
|
||||
|
|
@ -468,12 +479,6 @@ unsafe fn get_current_monitor() -> RootMonitorId {
|
|||
|
||||
impl Drop for Window2 {
|
||||
fn drop(&mut self) {
|
||||
// Remove this window from the `EventLoop`s list of windows.
|
||||
let id = self.id();
|
||||
if let Some(shared) = self.delegate.state.shared.upgrade() {
|
||||
shared.find_and_remove_window(id);
|
||||
}
|
||||
|
||||
// Close the window if it has not yet been closed.
|
||||
let nswindow = *self.window;
|
||||
if nswindow != nil {
|
||||
|
|
@ -652,7 +657,7 @@ impl Window2 {
|
|||
NSWindowStyleMask::NSMiniaturizableWindowMask |
|
||||
NSWindowStyleMask::NSResizableWindowMask |
|
||||
NSWindowStyleMask::NSTitledWindowMask
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let winit_window = Class::get("WinitWindow").unwrap_or_else(|| {
|
||||
|
|
@ -966,14 +971,14 @@ impl Window2 {
|
|||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let state = &self.delegate.state;
|
||||
let mut win_attribs = state.win_attribs.borrow_mut();
|
||||
|
||||
|
||||
if win_attribs.decorations == decorations {
|
||||
return;
|
||||
}
|
||||
|
||||
win_attribs.decorations = decorations;
|
||||
|
||||
// Skip modifiy if we are in fullscreen mode,
|
||||
// Skip modifiy if we are in fullscreen mode,
|
||||
// window_did_exit_fullscreen will handle it
|
||||
if win_attribs.fullscreen.is_some() {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ pub struct SavedWindowInfo {
|
|||
pub style: LONG,
|
||||
/// Window ex-style
|
||||
pub ex_style: LONG,
|
||||
/// Window position and size
|
||||
/// Window position and size
|
||||
pub rect: RECT,
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +326,13 @@ lazy_static! {
|
|||
winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
// Message sent by a `Window` when it wants to be destroyed by the main thread.
|
||||
// WPARAM and LPARAM are unused.
|
||||
pub static ref DESTROY_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// There's no parameters passed to the callback function, so it needs to get its context stashed
|
||||
|
|
@ -386,17 +393,26 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
|
|||
{
|
||||
match msg {
|
||||
winuser::WM_CLOSE => {
|
||||
use events::WindowEvent::Closed;
|
||||
use events::WindowEvent::CloseRequested;
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Closed
|
||||
event: CloseRequested
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_DESTROY => {
|
||||
use events::WindowEvent::Destroyed;
|
||||
CONTEXT_STASH.with(|context_stash| {
|
||||
let mut context_stash = context_stash.borrow_mut();
|
||||
context_stash.as_mut().unwrap().windows.remove(&window);
|
||||
});
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
},
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(window)),
|
||||
event: Destroyed
|
||||
});
|
||||
0
|
||||
},
|
||||
|
||||
winuser::WM_PAINT => {
|
||||
use events::WindowEvent::Refresh;
|
||||
|
|
@ -922,7 +938,12 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
|
|||
},
|
||||
|
||||
_ => {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
if msg == *DESTROY_MSG_ID {
|
||||
winuser::DestroyWindow(window);
|
||||
0
|
||||
} else {
|
||||
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::sync::Mutex;
|
|||
use std::sync::mpsc::channel;
|
||||
use std::cell::Cell;
|
||||
|
||||
use platform::platform::events_loop;
|
||||
use platform::platform::events_loop::{self, DESTROY_MSG_ID};
|
||||
use platform::platform::EventsLoop;
|
||||
use platform::platform::PlatformSpecificWindowBuilderAttributes;
|
||||
use platform::platform::WindowId;
|
||||
|
|
@ -47,14 +47,14 @@ unsafe impl Send for Window {}
|
|||
unsafe impl Sync for Window {}
|
||||
|
||||
// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903
|
||||
// The idea here is that we use the AdjustWindowRectEx function to calculate how much additional
|
||||
// non-client area gets added due to the styles we passed. To make the math simple,
|
||||
// we ask for a zero client rectangle, so that the resulting window is all non-client.
|
||||
// And then we pass in the empty rectangle represented by the dot in the middle,
|
||||
// and the AdjustWindowRectEx expands the rectangle in all dimensions.
|
||||
// We see that it added ten pixels to the left, right, and bottom,
|
||||
// The idea here is that we use the AdjustWindowRectEx function to calculate how much additional
|
||||
// non-client area gets added due to the styles we passed. To make the math simple,
|
||||
// we ask for a zero client rectangle, so that the resulting window is all non-client.
|
||||
// And then we pass in the empty rectangle represented by the dot in the middle,
|
||||
// and the AdjustWindowRectEx expands the rectangle in all dimensions.
|
||||
// We see that it added ten pixels to the left, right, and bottom,
|
||||
// and it added fifty pixels to the top.
|
||||
// From this we can perform the reverse calculation: Instead of expanding the rectangle, we shrink it.
|
||||
// From this we can perform the reverse calculation: Instead of expanding the rectangle, we shrink it.
|
||||
unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> BOOL {
|
||||
let mut rc: RECT = mem::zeroed();
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> B
|
|||
prc.right -= rc.right;
|
||||
prc.bottom -= rc.bottom;
|
||||
}
|
||||
|
||||
|
||||
frc
|
||||
}
|
||||
|
||||
|
|
@ -378,13 +378,13 @@ impl Window {
|
|||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
|
||||
window_state.attributes.maximized = maximized;
|
||||
window_state.attributes.maximized = maximized;
|
||||
// we only maximized if we are not in fullscreen
|
||||
if window_state.attributes.fullscreen.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let window = self.window.clone();
|
||||
|
||||
let window = self.window.clone();
|
||||
unsafe {
|
||||
// And because ShowWindow will resize the window
|
||||
// We call it in the main thread
|
||||
|
|
@ -620,9 +620,9 @@ impl Drop for Window {
|
|||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// We are sending WM_CLOSE, and our callback will process this by calling DefWindowProcW,
|
||||
// which in turn will send a WM_DESTROY.
|
||||
winuser::PostMessageW(self.window.0, winuser::WM_CLOSE, 0, 0);
|
||||
// The window must be destroyed from the same thread that created it, so we send a
|
||||
// custom message to be handled by our callback to do the actual work.
|
||||
winuser::PostMessageW(self.window.0, *DESTROY_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -770,7 +770,7 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild
|
|||
|
||||
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
|
||||
}
|
||||
|
||||
|
||||
let win = Window {
|
||||
window: real_window,
|
||||
window_state: window_state,
|
||||
|
|
@ -784,7 +784,7 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild
|
|||
}
|
||||
|
||||
inserter.insert(win.window.0, win.window_state.clone());
|
||||
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
|
|
@ -824,7 +824,7 @@ impl Drop for ComInitialized {
|
|||
}
|
||||
}
|
||||
|
||||
thread_local!{
|
||||
thread_local!{
|
||||
static COM_INITIALIZED: ComInitialized = {
|
||||
unsafe {
|
||||
combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED);
|
||||
|
|
@ -871,7 +871,7 @@ mod taskbar {
|
|||
fn MarkFullscreenWindow(
|
||||
hwnd: HWND,
|
||||
fFullscreen: BOOL,
|
||||
) -> HRESULT,
|
||||
) -> HRESULT,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue