diff --git a/examples/plugin_clack/src/audio.rs b/examples/plugin_clack/src/audio.rs index 8d5b9bf5..b3e34ee4 100644 --- a/examples/plugin_clack/src/audio.rs +++ b/examples/plugin_clack/src/audio.rs @@ -3,7 +3,7 @@ use clack_plugin::prelude::*; pub struct ExamplePluginAudioProcessor; -impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread> for ExamplePluginAudioProcessor { +impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread<'a>> for ExamplePluginAudioProcessor { fn activate( _host: HostAudioProcessorHandle<'a>, _main_thread: &mut ExamplePluginMainThread, _shared: &'a (), _audio_config: PluginAudioConfiguration, diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 56a33af6..1e4403a0 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -2,21 +2,22 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; use baseview::gl::GlConfig; -use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; +use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; +use baseview::{HandlerError, WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ - AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, - Window as ClapWindow, + AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, + PluginGuiImpl, Window as ClapWindow, }; use clack_plugin::plugin::PluginError; - +use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; #[allow(deprecated)] use raw_window_handle::HasRawWindowHandle; pub struct ExamplePluginGui { - handle: WindowHandle, + pub handle: WindowHandle, } -impl PluginGuiImpl for ExamplePluginMainThread { +impl PluginGuiImpl for ExamplePluginMainThread<'_> { fn is_api_supported(&mut self, configuration: GuiConfiguration) -> bool { !configuration.is_floating && Some(configuration.api_type) == GuiApiType::default_for_current_platform() @@ -97,7 +98,17 @@ impl PluginGuiImpl for ExamplePluginMainThread { .with_gl_config(GlConfig::default()) .with_parent(&parent); - let window = baseview::create_window(options, OpenWindowExample::new)?; + let mut host = Host::new().with_main_thread(unsafe { + MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } + }); + + if let Some(gui) = self.host_gui { + host = host.with_callbacks(unsafe { + HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() } + }); + } + + let window = baseview::create_window_with_host(options, OpenWindowExample::new, host)?; self.gui = Some(ExamplePluginGui { handle: window }); @@ -145,3 +156,31 @@ fn gui_size_to_window_size(size: GuiSize) -> Size { Size::Physical(PhysicalSize::new(size.width, size.height)) } } + +struct MainThreadHandler { + host: HostSharedHandle<'static>, +} + +impl HostMainThreadCaller for MainThreadHandler { + fn call_main_thread(&mut self) { + eprintln!("Requesting main thread"); + self.host.request_callback(); + } +} + +struct HostGuiCallbacks { + ext: HostGui, + host: HostMainThreadHandle<'static>, +} + +impl HostCallbacks for HostGuiCallbacks { + fn request_resize(&mut self, new_size: WindowSize) -> Result<(), HandlerError> { + let size = window_size_to_gui_size(new_size); + self.ext.request_resize(&self.host, size.width, size.height)?; + Ok(()) + } + + fn destroyed(&mut self) { + self.ext.closed(&self.host, true); + } +} diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index 4fb4855e..b300b33c 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -1,6 +1,6 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; -use clack_extensions::gui::PluginGui; +use clack_extensions::gui::{HostGui, PluginGui}; use clack_plugin::prelude::*; mod audio; @@ -15,7 +15,7 @@ pub struct ExamplePlugin; impl Plugin for ExamplePlugin { type AudioProcessor<'a> = ExamplePluginAudioProcessor; type Shared<'a> = (); - type MainThread<'a> = ExamplePluginMainThread; + type MainThread<'a> = ExamplePluginMainThread<'a>; fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { builder.register::(); @@ -35,20 +35,28 @@ impl DefaultPluginFactory for ExamplePlugin { } fn new_main_thread<'a>( - _host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>, + host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>, ) -> Result, PluginError> { - Ok(Self::MainThread { gui: None }) + Ok(Self::MainThread { gui: None, host_gui: host.get_extension(), host }) } } /// The data that belongs to the main thread of our plugin. -pub struct ExamplePluginMainThread { +pub struct ExamplePluginMainThread<'a> { + /// The host handle + host: HostMainThreadHandle<'a>, + // The host GUI extension handle + host_gui: Option, /// The plugin's GUI state and context gui: Option, } -impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread { - fn on_main_thread(&mut self) {} +impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> { + fn on_main_thread(&mut self) { + if let Some(gui) = self.gui.as_mut() { + gui.handle.host_main_thread_callback(); + } + } } clack_export_entry!(SinglePluginEntry); diff --git a/examples/plugin_clack/src/window_handler.rs b/examples/plugin_clack/src/window_handler.rs index 36abc94e..16800ba9 100644 --- a/examples/plugin_clack/src/window_handler.rs +++ b/examples/plugin_clack/src/window_handler.rs @@ -1,6 +1,7 @@ use baseview::dpi::PhysicalPosition; use baseview::{ - Event, EventStatus, HandlerError, MouseEvent, WindowContext, WindowHandler, WindowSize, + Event, EventStatus, HandlerError, MouseButton, MouseEvent, WindowContext, WindowHandler, + WindowSize, }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -111,6 +112,16 @@ impl WindowHandler for OpenWindowExample { self.is_cursor_inside.set(false); self.damaged.set(true); } + Event::Mouse(MouseEvent::ButtonPressed { button: MouseButton::Left, .. }) => { + let mut size = self.window_context.size().physical; + if self.mouse_pos.get().x < 100.0 { + size.width -= 50; + } else { + size.width += 50; + } + + self.window_context.resize(size).unwrap(); + } _ => {} } diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 00000000..11cbd37c --- /dev/null +++ b/src/host.rs @@ -0,0 +1,122 @@ +use crate::{HandlerError, WindowSize}; +use std::cell::RefCell; + +/// A special handler for the Window thread to wake up and call methods on the main thread. +/// +/// [`WindowHandle::host_main_thread_callback`](crate::WindowHandle::host_main_thread_callback) +/// should be called as a response to this. +/// +/// # Platform compatibility notes +/// +/// This is only needed on X11, as Windows and macOS windows already run on the main thread. +pub trait HostMainThreadCaller: Send + 'static { + /// Schedules a callback on the main thread. + /// + /// [`WindowHandle::host_main_thread_callback`](crate::WindowHandle::host_main_thread_callback) + /// should be called as a response to this. + /// + /// # Platform compatibility notes + /// + /// Only X11 needs this. This can be implemented as a no-op on Windows and macOS. + fn call_main_thread(&mut self); +} + +/// A handler for baseview windows to interact with their host. +pub trait HostCallbacks: 'static { + /// Requests the parent window to be resized to accommodate the child window with the given new + /// size. + /// + /// # Errors + /// + /// This can return any type of error, indicating the host either failed or denied to handle the + /// resize request. + /// If it does, the error is logged and the resize operation is canceled or reverted. + fn request_resize(&mut self, new_size: WindowSize) -> Result<(), HandlerError>; + /// Notifies the host that the child window has been destroyed for a reason outside the host's + /// control. + /// + /// This can be because the display connection was lost, because the window handler crashed, or + /// because the window handler decided to close the window itself. + /// + /// The host should close its parent window, as it will not show anything useful anymore. + fn destroyed(&mut self); +} + +/// Configuration and callbacks for a window's host. +/// +/// # Safety +/// +/// This type and its methods are always safe to use. +/// +/// It also brings the additional safety guarantee that all handlers given to this types will be +/// destroyed alongside with the window, guaranteeing callbacks cannot be fired after the +/// [`WindowHandle`](crate::WindowHandle) is dropped. (or after this [`Host`] object is dropped, if +/// it never made it to a [`create_window_with_host`](crate::create_window_with_host) call). +pub struct Host { + #[cfg(target_os = "linux")] + pub(crate) main_thread: Option>, + pub(crate) callbacks: Option>>, +} + +impl Default for Host { + fn default() -> Self { + Self::new() + } +} + +impl Host { + /// Creates a new, empty host with no callbacks. + /// + /// Calling [`create_window_with_host`](crate::create_window_with_host) with this is equivalent + /// to just calling [`create_window`](crate::create_window). + #[inline] + pub fn new() -> Self { + Self { + #[cfg(target_os = "linux")] + main_thread: None, + callbacks: None, + } + } + + /// Sets the [`HostMainThreadCaller`] handler to be used. + /// + /// If another callback handler was already set, it is replaced. + /// + /// # Platform Compatibility notes + /// + /// This is only useful on X11. On Window and macOS, this is a no-op. + #[inline] + #[allow(unused)] + pub fn with_main_thread(mut self, main_thread: impl HostMainThreadCaller) -> Self { + #[cfg(target_os = "linux")] + { + self.main_thread = Some(Box::new(main_thread)); + } + + self + } + + /// Sets the [`HostCallbacks`] handler to be used. + /// + /// If another callback handler was already set, it is replaced. + #[inline] + pub fn with_callbacks(mut self, callbacks: impl HostCallbacks) -> Self { + self.callbacks = Some(RefCell::new(Box::new(callbacks))); + self + } +} + +#[allow(unused)] +impl Host { + pub(crate) fn notify_destroyed(&self) { + let Some(callbacks) = &self.callbacks else { return }; + let Ok(mut callbacks) = callbacks.try_borrow_mut() else { return }; + callbacks.destroyed(); + } + + pub(crate) fn request_resize(&self, new_size: WindowSize) -> Result<(), HandlerError> { + let Some(callbacks) = &self.callbacks else { return Ok(()) }; + let Ok(mut callbacks) = callbacks.try_borrow_mut() else { return Ok(()) }; + callbacks.request_resize(new_size) + } +} diff --git a/src/lib.rs b/src/lib.rs index b5aa10ca..c97c11cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod context; mod error; mod event; mod handler; +pub mod host; mod keyboard; mod mouse_cursor; mod tracing; diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index 7eab3dcf..6c78b956 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -31,7 +31,7 @@ impl WindowContext { pub fn request_close(&self) { let Some(view) = self.view.load() else { return }; let Some(view) = view.inner_ref() else { return }; - BaseviewView::close(view); + BaseviewView::close(view, false); } pub fn has_focus(&self) -> bool { @@ -67,7 +67,7 @@ impl WindowContext { return Ok(()); } - BaseviewView::resize(view, size); + BaseviewView::resize(view, size, true); Ok(()) } diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 836515db..115c6fff 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -3,6 +3,7 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; use crate::handler::WindowHandlerBuilder; +use crate::host::Host; use crate::platform::*; use crate::tracing::warn; use crate::wrappers::appkit::*; @@ -41,6 +42,8 @@ pub(crate) struct BaseviewView { parenting: ViewParentingType, + host: Host, + #[cfg(feature = "opengl")] pub(crate) gl_context: std::cell::OnceCell, } @@ -48,7 +51,7 @@ pub(crate) struct BaseviewView { impl BaseviewView { pub fn new( _options: WindowOpenOptions, builder: WindowHandlerBuilder, parenting: ViewParentingType, - final_size: LogicalSize, mtm: MainThreadMarker, + host: Host, final_size: LogicalSize, mtm: MainThreadMarker, ) -> Result<(Retained>, Rc)> { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); @@ -64,6 +67,7 @@ impl BaseviewView { window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), parenting, + host, #[cfg(feature = "opengl")] gl_context: std::cell::OnceCell::new(), @@ -131,7 +135,7 @@ impl BaseviewView { Ok((view, state)) } - pub fn close(this: ViewRef) { + pub fn close(this: ViewRef, from_host: bool) { this.state.closed.set(true); this.view.removeFromSuperview(); this.notification_center_observer.take(); @@ -149,9 +153,13 @@ impl BaseviewView { app.stop(Some(&app)); } } + + if !from_host { + this.host.notify_destroyed(); + } } - pub fn resize(this: ViewRef, size: Size) { + pub fn resize(this: ViewRef, size: Size, notify_host: bool) { let size = size.to_logical::(this.view.backing_scale_factor()); // NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even // though the size is in fractional pixels. @@ -174,7 +182,7 @@ impl BaseviewView { } } - Self::view_did_change_backing_properties(this); + Self::view_did_change_backing_properties(this, notify_host); } /// Trigger the event immediately and return the event status. @@ -185,7 +193,7 @@ impl BaseviewView { fn trigger_frame(this: ViewRef) { if let Some(Err(e)) = this.window_handler.use_handler(|h| h.on_frame()) { warn!("Error while rendering frame: {}", e); - Self::close(this); + Self::close(this, false); } } } @@ -216,12 +224,12 @@ impl ViewImpl for BaseviewView { fn window_should_close(this: ViewRef) -> bool { Self::trigger_event(this, Event::Window(WindowEvent::WillClose)); - Self::close(this); + Self::close(this, false); true } - fn view_did_change_backing_properties(this: ViewRef) { + fn view_did_change_backing_properties(this: ViewRef, notify_host: bool) { let current_size = this.view.size(); let current_scale_factor = this.view.backing_scale_factor(); @@ -232,15 +240,24 @@ impl ViewImpl for BaseviewView { { let previous = this.state.size.replace(current_size); this.state.scale_factor.set(current_scale_factor); + let new_size = WindowSize::from_logical(current_size, current_scale_factor); - let result = this.window_handler.use_handler(|h| { - h.resized(WindowSize::from_logical(current_size, current_scale_factor)) - }); + let result = this.window_handler.use_handler(|h| h.resized(new_size)); if let Some(Err(e)) = result { warn!("Window Handler failed to resize: {}", e); this.state.size.set(previous); - Self::resize(this, previous.into()) + + Self::resize(this, previous.into(), false); + return; + } + + if notify_host { + if let Err(e) = this.host.request_resize(new_size) { + warn!("Host failed to resize parent view: {}", e); + + Self::resize(this, previous.into(), false); + } } } } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 49493fbe..d6f31033 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -7,6 +7,7 @@ use std::cell::Cell; use std::rc::Rc; use crate::handler::WindowHandlerBuilder; +use crate::host::Host; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::platform::Result; use crate::wrappers::appkit::{create_window, View}; @@ -24,13 +25,13 @@ impl Drop for WindowHandle { let Some(view) = self.view.load() else { return }; let Some(view) = view.inner_ref() else { return }; - BaseviewView::close(view); + BaseviewView::close(view, true); } } impl WindowHandle { pub fn create_window( - mut options: WindowOpenOptions, handler: WindowHandlerBuilder, + mut options: WindowOpenOptions, handler: WindowHandlerBuilder, host: Host, ) -> Result { autoreleasepool(|_| { let Some(mtm) = MainThreadMarker::new() else { @@ -41,16 +42,16 @@ impl WindowHandle { let _ = NSApplication::sharedApplication(mtm); if let Some(parent) = options.parent.take() { - return Self::create_window_parented(options, handler, parent.view, mtm); + return Self::create_window_parented(options, handler, host, parent.view, mtm); } - Self::create_window_standalone(options, handler, mtm) + Self::create_window_standalone(options, handler, host, mtm) }) } pub fn create_window_parented( - builder: WindowOpenOptions, handler: WindowHandlerBuilder, parent_view: Retained, - mtm: MainThreadMarker, + builder: WindowOpenOptions, handler: WindowHandlerBuilder, host: Host, + parent_view: Retained, mtm: MainThreadMarker, ) -> Result { let parenting = ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; @@ -59,13 +60,15 @@ impl WindowHandle { parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); let final_size = builder.size.to_logical(backing_scale_factor); - let (ns_view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm)?; + let (ns_view, state) = + BaseviewView::new(builder, handler, parenting, host, final_size, mtm)?; Ok(Self { mtm, state, _window: None, view: Weak::from_retained(&ns_view) }) } pub fn create_window_standalone( - builder: WindowOpenOptions, handler: WindowHandlerBuilder, mtm: MainThreadMarker, + builder: WindowOpenOptions, handler: WindowHandlerBuilder, host: Host, + mtm: MainThreadMarker, ) -> Result { let app = NSApplication::sharedApplication(mtm); let window = create_window_with_options(&builder, mtm); @@ -78,7 +81,7 @@ impl WindowHandle { owned_window: Weak::from_retained(&window), }; - let (view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm)?; + let (view, state) = BaseviewView::new(builder, handler, parenting, host, final_size, mtm)?; Ok(Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) }) } @@ -92,6 +95,11 @@ impl WindowHandle { self.state.closed.get() } + #[inline] + pub fn handle_main_thread_callback(&self) { + // No-op + } + pub fn size(&self) -> WindowSize { WindowSize::from_logical(self.state.size.get(), self.state.scale_factor.get()) } @@ -100,7 +108,7 @@ impl WindowHandle { let Some(view) = self.view.load() else { return Ok(()) }; let Some(view) = view.inner_ref() else { return Ok(()) }; - BaseviewView::resize(view, size); + BaseviewView::resize(view, size, false); Ok(()) } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 9e8d81d2..f3644bdb 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -13,7 +13,7 @@ use windows_sys::Win32::{ }, }; -use crate::warn; +use crate::{warn, HandlerError}; use dpi::{PhysicalPosition, PhysicalSize, Size}; use std::cell::Cell; use std::num::NonZeroUsize; @@ -23,6 +23,7 @@ pub(crate) const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; +use crate::host::Host; use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::Error; use crate::wrappers::win32::cursor::SystemCursor; @@ -72,6 +73,7 @@ impl WindowHandle { pub fn resize(&self, new_size: Size) -> Result<()> { let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; let new_size = new_size.to_physical(self.state.scale_factor()); + let _guard = self.state.originate_host_resize(); hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &self.state.user32)?; if self.state.current_size.get() == new_size { @@ -104,6 +106,8 @@ impl WindowHandle { return Ok(()); } + let _guard = self.state.originate_host_resize(); + hwnd.resize_and_activate(new_size, None, &self.state.user32)?; if self.state.current_size.get() == new_size { @@ -112,12 +116,20 @@ impl WindowHandle { Err(Error::ResizeFailed) } } + + #[inline] + pub fn handle_main_thread_callback(&self) { + // No-op + } } impl Drop for WindowHandle { fn drop(&mut self) { if let Some(hwnd) = self.hwnd.take() { - let _ = hwnd.destroy(); + let _guard = self.state.originate_host_destroy(); + if let Err(e) = hwnd.destroy() { + warn!("Failed to destroy window: {}", e); + } } } } @@ -128,6 +140,7 @@ pub struct BaseviewWindow { initial_size: Size, handler_builder: Cell>, + host: Host, // Things not directly used, but kept so their Drop impl runs when the window is destroyed _keyboard_hook: Cell>, @@ -137,9 +150,30 @@ pub struct BaseviewWindow { pub gl_config: Option, } +impl BaseviewWindow { + fn notify_destroyed_to_host(&self) { + if self.shared_state.destroy_host_originated.get() { + return; + }; + + self.host.notify_destroyed() + } + + fn request_resize_from_host( + &self, new_size: WindowSize, + ) -> core::result::Result<(), HandlerError> { + if self.shared_state.resize_host_originated.get() { + return Ok(()); + }; + + self.host.request_resize(new_size) + } +} + impl Drop for BaseviewWindow { fn drop(&mut self) { self.shared_state.is_alive.set(false); + self.notify_destroyed_to_host(); } } @@ -198,7 +232,7 @@ impl WindowImpl for BaseviewWindow { unsafe fn handle_message( &self, window: HWnd, msg: u32, wparam: WPARAM, lparam: LPARAM, ) -> Option { - unsafe { wnd_proc_inner(window, msg, wparam, lparam, &self.window_state) } + unsafe { wnd_proc_inner(window, msg, wparam, lparam, self) } } fn before_destroy(&self, window: HWnd) { @@ -209,8 +243,9 @@ impl WindowImpl for BaseviewWindow { /// Our custom `wnd_proc` handler. If the result contains a value, then this is returned after /// handling any deferred tasks. otherwise the default window procedure is invoked. unsafe fn wnd_proc_inner( - window: HWnd, msg: u32, wparam: WPARAM, lparam: LPARAM, window_state: &WindowState, + window: HWnd, msg: u32, wparam: WPARAM, lparam: LPARAM, window_bv: &BaseviewWindow, ) -> Option { + let window_state = &window_bv.window_state; match msg { WM_MOUSEMOVE => { if window_state.mouse_was_outside_window.get() { @@ -398,6 +433,21 @@ unsafe fn wnd_proc_inner( return Some(-1); } + if let Err(e) = window_bv.request_resize_from_host(new_size) { + warn!("Resize request from Host failed: {}. Reverting to previous size.", e); + + if let Err(e) = handler.resized(new_size) { + warn!("Window Handler failed to resize to previous window size: {}", e); + } + + window_state.shared.current_size.set(previous); + if let Err(e) = window_state.resize(previous.into()) { + warn!("Failed to resize back to previous window size: {}", e); + } + + return Some(-1); + } + None } WM_DPICHANGED => { @@ -433,6 +483,21 @@ unsafe fn wnd_proc_inner( warn!("Failed to resize back to previous window size: {}", e); } } + + if let Err(e) = window_bv.request_resize_from_host(new_size) { + warn!("Resize request from Host failed: {}. Reverting to previous size.", e); + + if let Err(e) = handler.resized(new_size) { + warn!("Window Handler failed to resize to previous window size: {}", e); + } + + window_state.shared.current_size.set(previous_size); + if let Err(e) = window_state.resize(previous_size.into()) { + warn!("Failed to resize back to previous window size: {}", e); + } + + return Some(-1); + } } None @@ -465,7 +530,7 @@ unsafe fn wnd_proc_inner( impl WindowHandle { pub fn create_window( - options: WindowOpenOptions, build: WindowHandlerBuilder, + options: WindowOpenOptions, build: WindowHandlerBuilder, host: Host, ) -> Result { let extended_user_32 = ExtendedUser32::load()?; let title = HSTRING::from(options.title); @@ -495,6 +560,7 @@ impl WindowHandle { initial_size: options.size, handler_builder: Cell::new(Some(build)), shared_state, + host, _drop_target: None.into(), _keyboard_hook: None.into(), diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 65eb231b..8dd84f23 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -146,6 +146,8 @@ pub struct WindowSharedState { pub current_size: Cell>, pub current_dpi: Cell>, // None if Win32 HiDPI isn't supported pub fallback_scale_factor: Cell>, + pub resize_host_originated: Cell, + pub destroy_host_originated: Cell, pub user32: ExtendedUser32, } @@ -157,6 +159,8 @@ impl WindowSharedState { current_dpi: None.into(), current_size: current_size.into(), fallback_scale_factor: None.into(), + resize_host_originated: false.into(), + destroy_host_originated: false.into(), user32, } .into() @@ -173,4 +177,21 @@ impl WindowSharedState { self.fallback_scale_factor.get().unwrap_or(1.0) } } + + pub fn originate_host_resize(&self) -> impl Drop + use<'_> { + self.resize_host_originated.set(true); + Guard(&self.resize_host_originated) + } + + pub fn originate_host_destroy(&self) -> impl Drop + use<'_> { + self.destroy_host_originated.set(true); + Guard(&self.destroy_host_originated) + } +} + +struct Guard<'a>(&'a Cell); +impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + self.0.set(false); + } } diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index a5c0674c..debf1678 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,15 +1,41 @@ use crate::platform::x11::drag_n_drop::ParseError; +use crate::platform::x11::window_thread::RequestFailed; use crate::platform::x11::xcb_connection::GetPropertyError; use crate::warn; use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; -use std::sync::mpsc::RecvError; +use std::fmt::{Display, Formatter}; use x11_dl::error::OpenError; use x11rb::connection::RequestConnection; use x11rb::cookie::{Cookie, VoidCookie}; use x11rb::errors::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError}; use x11rb::x11_utils::{TryParse, X11Error}; +#[derive(Debug)] +pub enum FatalError { + Connection(ConnectionError), + SendMainThread, +} + +impl Display for FatalError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FatalError::Connection(e) => e.fmt(f), + FatalError::SendMainThread => { + f.write_str("Failed to send callback from X11 thread to main thread") + } + } + } +} + +impl std::error::Error for FatalError {} + +impl From for FatalError { + fn from(err: ConnectionError) -> FatalError { + FatalError::Connection(err) + } +} + #[derive(Debug)] pub enum Error { CreationFailed(String), @@ -25,20 +51,40 @@ pub enum Error { Connect(ConnectError), DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), - Channel(RecvError), + MainThreadRecvResult, Calloop(calloop::Error), + RequestFromMainThreadFailed(RequestFailed), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[cfg(feature = "opengl")] Gl(super::gl::CreationFailedError), } -impl std::fmt::Display for Error { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::Io(e) => e.fmt(f), Self::IdsExhausted => f.write_str("X11 IDs have been exhausted"), - _ => todo!(), + Error::CreationFailed(e) => write!(f, "Failed to create window: {e}"), + Error::Run(e) => write!(f, "Error in running X11 thread: {e}"), + Error::DylibOpen(e) => e.fmt(f), + Error::InitThreadsFailed(e) => e.fmt(f), + Error::X11(e) => write!(f, "X server replied with error: {e:?}"), + Error::Connection(e) => e.fmt(f), + Error::Parse(e) => e.fmt(f), + Error::GetProperty(e) => e.fmt(f), + Error::Connect(e) => e.fmt(f), + Error::DisplayOpenFailed(e) => e.fmt(f), + Error::Handler(e) => e.fmt(f), + Error::MainThreadRecvResult => { + f.write_str("Failed to receive Window creation response from X11 thread: channel was closed unexpectedly") + } + Error::Calloop(e) => e.fmt(f), + Error::RequestFromMainThreadFailed(e) => e.fmt(f), + #[cfg(feature = "opengl")] + Error::XLib(e) => e.fmt(f), + #[cfg(feature = "opengl")] + Error::Gl(e) => e.fmt(f), } } } @@ -47,7 +93,6 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Io(e) => Some(e), - Error::Channel(e) => Some(e), Error::DylibOpen(e) => Some(e), Error::Connect(e) => Some(e), Error::Handler(e) => Some(e.source()), @@ -106,9 +151,9 @@ impl From for Error { } } -impl From for Error { - fn from(value: RecvError) -> Self { - Self::Channel(value) +impl From for Error { + fn from(value: RequestFailed) -> Self { + Self::RequestFromMainThreadFailed(value) } } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index db9a9170..a093ac7e 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -3,8 +3,10 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo use super::*; use std::result::Result; +use crate::host::HostMainThreadCaller; +use crate::platform::x11::error::FatalError; use crate::platform::x11::window_thread::{ - WindowThreadRequest, WindowThreadResponse, WindowThreadResponseMessage, + HostCallback, WindowThreadRequest, WindowThreadResponse, WindowThreadResponseMessage, }; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; @@ -15,11 +17,36 @@ use calloop::{Interest, LoopSignal, Mode, PostAction}; use dpi::{PhysicalPosition, PhysicalSize}; use std::rc::Rc; use std::sync::mpsc; +use std::sync::mpsc::Receiver; use std::time::{Duration, Instant}; use x11rb::connection::Connection; use x11rb::errors::ConnectionError; use x11rb::protocol::Event as XEvent; +pub struct MainThreadCaller { + sender: mpsc::Sender, + caller: Box, +} + +impl MainThreadCaller { + pub(crate) fn new( + main_thread: Option>, + ) -> (Option, Option>) { + let Some(main_thread) = main_thread else { + return (None, None); + }; + + let (sender, receiver) = mpsc::channel(); + (Some(Self { sender, caller: main_thread }), Some(receiver)) + } + + pub fn send(&mut self, msg: HostCallback) -> Result<(), FatalError> { + self.sender.send(msg).map_err(|_| FatalError::SendMainThread)?; + self.caller.call_main_thread(); + Ok(()) + } +} + pub(crate) struct EventLoop { handler: Box, window: Rc, @@ -34,6 +61,7 @@ pub(crate) struct EventLoop { run_error: Option, response_sender: mpsc::Sender, + main_thread: Option, } const FRAME_INTERVAL: Duration = Duration::from_millis(15); @@ -43,7 +71,7 @@ impl EventLoop { window: Rc, handler: Box, request_receiver: calloop::channel::Channel, response_sender: mpsc::Sender, - inner: &mut calloop::EventLoop<'static, Self>, + main_thread: Option, inner: &mut calloop::EventLoop<'static, Self>, ) -> Result { let loop_handle = inner.handle(); @@ -69,13 +97,15 @@ impl EventLoop { drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), run_error: None, + main_thread, + window, response_sender, }) } #[inline] - fn drain_xcb_events(&mut self) -> Result<(), ConnectionError> { + fn drain_xcb_events(&mut self) -> Result<(), FatalError> { // the X server has a tendency to send spurious/extraneous configure notify events when a // window is resized, and we need to batch those together and just send one resize event // when they've all been coalesced. @@ -85,16 +115,33 @@ impl EventLoop { self.handle_xcb_event(event)?; } - if let Some(size) = self.new_physical_size.take() { - let previous = self.window.store_size(size); + self.handle_coalesced_resize_events() + } + + fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> { + let Some(new_size) = self.new_physical_size.take() else { return Ok(()) }; + let previous = self.window.store_size(new_size); + + if previous == new_size { + return Ok(()); + }; + + let scale_factor = self.window.scaling_factor.get(); + let new_size = WindowSize::from_physical(new_size.cast(), scale_factor); - let scale_factor = self.window.scaling_factor.get(); - if let Err(e) = - self.handler.resized(WindowSize::from_physical(size.cast(), scale_factor)) - { - warn!("Window Handler failed to resize: {}", e); - self.window.store_size(previous); - self.window.xcb_window.resize(previous.cast())?.check_warn(); + if let Err(e) = self.handler.resized(new_size) { + warn!("Window Handler failed to resize: {}", e); + self.window.store_size(previous); + self.window.xcb_window.resize(previous.cast())?.check_warn(); + } else { + // Host requests use resize_immediately, which stops the previous == new_size condition + // So if we're here, it's guaranteed not to be from a host request + + if let Some(host) = self.main_thread.as_mut() { + host.send(HostCallback::Resized { + new_size, + previous: WindowSize::from_physical(previous.cast(), scale_factor), + })?; } } @@ -157,7 +204,7 @@ impl EventLoop { } } - fn handle_connection_event_ready(&mut self) -> Result { + fn handle_connection_event_ready(&mut self) -> Result { self.drain_xcb_events()?; Ok(PostAction::Continue) @@ -194,6 +241,15 @@ impl EventLoop { self.handle_event(Event::Window(WindowEvent::WillClose)); + // If the event loop doesn't stop because the host asked it to, then we should notify it + if !self.window.main_thread_shared.is_stop_host_requested() { + if let Some(main_thread) = self.main_thread.as_mut() { + if let Err(e) = main_thread.send(HostCallback::Destroyed) { + warn!("Could not notify host that X11 thread is stopping: {}", e) + } + } + } + if let Some(err) = self.run_error { return Err(err); }; diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index 29b73a36..b4d4c532 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -20,6 +20,24 @@ pub enum CreationFailedError { OpenError(OpenError), } +impl Display for CreationFailedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CreationFailedError::NoValidFBConfig => { + f.write_str("Could not find a valid Framebuffer configuration") + } + CreationFailedError::NoVisual => { + f.write_str("Could not find a matching visual configuration") + } + CreationFailedError::GetProcAddressFailed => f.write_str("GetProcAddress failed"), + CreationFailedError::MakeCurrentFailed => f.write_str("MakeCurrent failed"), + CreationFailedError::ContextCreationFailed => f.write_str("Faile to create GL context"), + CreationFailedError::X11Error(e) => e.fmt(f), + CreationFailedError::OpenError(e) => e.fmt(f), + } + } +} + pub type GlContext = Rc; pub struct GlContextInner { diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 7e44150e..990af08b 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -61,7 +61,7 @@ pub(crate) struct WindowInner { pub(crate) is_focused: Cell, pub(crate) loop_signal: LoopSignal, - main_thread_shared: Arc, + pub(crate) main_thread_shared: Arc, } impl WindowInner { @@ -188,7 +188,7 @@ impl WindowInner { pub fn resize(&self, size: Size) -> Result<()> { let new_physical_size = size.to_physical(self.scaling_factor.get()); - self.xcb_window.resize(new_physical_size)?; + self.xcb_window.resize(new_physical_size)?.check()?; // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` // and notify the window handler about it @@ -205,8 +205,6 @@ impl WindowInner { return Ok(()); }; - self.xcb_window.resize(new_size.cast())?; // Will not call handler, as size is the same as above. - if let Err(e) = handler.resized(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) { @@ -215,6 +213,10 @@ impl WindowInner { return Err(e.into()); } + self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. + + // These come from the Host, no need to notify it about the new size + Ok(()) } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index f0cefc50..a4d179ba 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -1,7 +1,9 @@ use super::*; use crate::handler::WindowHandlerBuilder; -use crate::platform::x11::event_loop::EventLoop; +use crate::host::{Host, HostCallbacks}; +use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; use crate::platform::x11::window_shared::WindowInner; +use crate::warn; use crate::{WindowContext, WindowOpenOptions, WindowSize}; use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; @@ -18,6 +20,7 @@ pub(crate) struct WindowThreadShared { scaling_factor: AtomicU64, size: AtomicU32, final_error: Mutex>, + stopped_requested_from_host: AtomicBool, } impl WindowThreadShared { @@ -27,6 +30,7 @@ impl WindowThreadShared { final_error: None.into(), size: 0.into(), scaling_factor: 0.into(), + stopped_requested_from_host: false.into(), } } @@ -51,6 +55,10 @@ impl WindowThreadShared { self.scaling_factor .store(u64::from_be_bytes(scale_factor.to_ne_bytes()), Ordering::Relaxed); } + + pub fn is_stop_host_requested(&self) -> bool { + self.stopped_requested_from_host.load(Ordering::Relaxed) + } } pub enum WindowThreadRequest { @@ -64,6 +72,11 @@ pub enum WindowThreadResponse { pub type WindowThreadResponseMessage = core::result::Result; +pub enum HostCallback { + Resized { new_size: WindowSize, previous: WindowSize }, + Destroyed, +} + pub struct WindowThreadHandle { shared: Arc, loop_signal: LoopSignal, @@ -71,16 +84,19 @@ pub struct WindowThreadHandle { request_sender: calloop::channel::SyncSender, response_receiver: mpsc::Receiver, + callback_receiver: Option>, + host_callbacks: Option>, } impl WindowThreadHandle { pub fn create_window( - options: WindowOpenOptions, handler: WindowHandlerBuilder, + options: WindowOpenOptions, handler: WindowHandlerBuilder, host: Host, ) -> Result { let (tx, rx) = result_channel(); let shared = Arc::new(WindowThreadShared::new()); let (request_sender, request_receiver) = calloop::channel::sync_channel(1); let (response_sender, response_receiver) = mpsc::channel(); + let (main_thread_caller, main_thread_receiver) = MainThreadCaller::new(host.main_thread); let join_handle = { let shared = shared.clone(); @@ -92,6 +108,7 @@ impl WindowThreadHandle { shared, request_receiver, response_sender, + main_thread_caller, ) { Err(e) => return tx.send_error(e), Ok(thread) => thread, @@ -111,6 +128,8 @@ impl WindowThreadHandle { loop_signal, request_sender, response_receiver, + host_callbacks: host.callbacks.map(|c| c.into_inner()), + callback_receiver: main_thread_receiver, }) } @@ -130,12 +149,12 @@ impl WindowThreadHandle { } fn request(&self, req: WindowThreadRequest) -> Result<()> { - self.request_sender.send(req).unwrap(); // TODO: handle error - let result = self.response_receiver.recv().unwrap(); // TODO: handle error + self.request_sender.send(req).map_err(|_| RequestFailed::Send)?; + let result = self.response_receiver.recv().map_err(|_| RequestFailed::Recv)?; match result { Ok(WindowThreadResponse::Ok) => Ok(()), - Err(e) => Err(Error::Run(e)), // TODO: better error type? + Err(e) => Err(RequestFailed::Response(e).into()), } } @@ -156,15 +175,45 @@ impl WindowThreadHandle { pub fn is_open(&self) -> bool { !self.shared.stopped.load(Ordering::Relaxed) } + + pub fn handle_main_thread_callback(&mut self) { + loop { + let Some(receiver) = self.callback_receiver.as_mut() else { return }; + let Some(callback) = receiver.try_recv().ok() else { return }; + + self.handle_main_thread_message(callback); + } + } + + fn handle_main_thread_message(&mut self, msg: HostCallback) { + let Some(host_callbacks) = self.host_callbacks.as_mut() else { return }; + + match msg { + HostCallback::Destroyed => host_callbacks.destroyed(), + HostCallback::Resized { new_size: new, previous } => { + if let Err(e) = host_callbacks.request_resize(new) { + warn!("Host failed to resize parent window: {}. Reverting.", e); + + if let Err(e) = self.resize(previous.physical.into()) { + warn!( + "Failed to revert to previous size while handling previous error: {}", + e + ); + } + } + } + } + } } impl Drop for WindowThreadHandle { fn drop(&mut self) { + self.shared.stopped_requested_from_host.store(true, Ordering::Relaxed); self.loop_signal.stop(); self.loop_signal.wakeup(); if let Err(e) = self.run_until_closed() { - crate::warn!("Error while closing window: {}", e) + warn!("Error while closing window: {}", e) } } } @@ -180,16 +229,35 @@ struct WindowThread { shared: Arc, } +#[derive(Debug)] +pub enum RequestFailed { + Send, + Recv, + Response(String), +} + +impl Display for RequestFailed { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RequestFailed::Send => f.write_str("Request to X11 thread failed: Could not send request (X11 thread disconnected)"), + RequestFailed::Recv => f.write_str("Request to X11 thread failed: Could not receive response (X11 thread disconnected)"), + RequestFailed::Response(e) => f.write_str(e), + } + } +} + impl WindowThread { pub fn create( options: WindowOpenOptions, handler: WindowHandlerBuilder, shared: Arc, receiver: calloop::channel::Channel, sender: mpsc::Sender, + main_thread_caller: Option, ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; let inner = WindowInner::create(options, &ev_loop, shared.clone())?; let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new(inner, handler, receiver, sender, &mut ev_loop)?; + let event_loop = + EventLoop::new(inner, handler, receiver, sender, main_thread_caller, &mut ev_loop)?; Ok(Self { event_loop, ev_loop, shared }) } @@ -230,7 +298,8 @@ impl WindowResultSender { struct WindowResultReceiver(mpsc::Receiver); impl WindowResultReceiver { pub fn receive(self) -> Result { - let result = self.0.recv()?; + let result = self.0.recv().map_err(|_| Error::MainThreadRecvResult)?; + match result { WindowOpenResult::Error(e) => Err(Error::CreationFailed(e)), WindowOpenResult::Success { loop_signal } => Ok(loop_signal), diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index 7e012645..d07560bf 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -1,3 +1,4 @@ +use crate::platform::x11::error::CookieExt; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::X11Connection; use dpi::PhysicalSize; @@ -111,8 +112,9 @@ impl XcbWindow { impl Drop for XcbWindow { fn drop(&mut self) { - // TODO: log error - let Ok(cookie) = self.connection.conn.destroy_window(self.window_id.get()) else { return }; - let _ = cookie.check(); + match self.connection.conn.destroy_window(self.window_id.get()) { + Err(e) => crate::warn!("Failed to send request to destroy X window: {}", e), + Ok(cookie) => cookie.check_warn(), + } } } diff --git a/src/tracing.rs b/src/tracing.rs index 6eea95c7..414afea1 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -7,8 +7,8 @@ pub use tracing::{error, warn}; mod tracing_impl { macro_rules! __warn { ($($f:tt)*) => { - #[allow(unused, dead_code)] { + #[allow(unused, dead_code)] let _ = ($($f)*); } }; diff --git a/src/window.rs b/src/window.rs index 6fd97b69..3dab4905 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,4 +1,5 @@ use crate::handler::WindowHandlerBuilder; +use crate::host::Host; use crate::platform; use crate::*; use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; @@ -11,16 +12,19 @@ pub struct WindowHandle { } impl WindowHandle { + #[inline] fn new(window_handle: platform::WindowHandle) -> Self { Self { window_handle, phantom: PhantomData } } + #[inline] pub fn run_until_closed(self) -> Result<(), Error> { self.window_handle.run_until_closed()?; Ok(()) } /// The current size of the window. + #[inline] pub fn size(&self) -> WindowSize { self.window_handle.size() } @@ -28,6 +32,7 @@ impl WindowHandle { /// Resizes the window to the given [`Size`]. /// /// The `size` can be provided in either physical or logical pixels. + #[inline] pub fn resize(&self, size: Size) -> Result<(), Error> { self.window_handle.resize(size)?; Ok(()) @@ -47,6 +52,7 @@ impl WindowHandle { /// On X11, this value is used if no `Xft.dpi`setting is set. /// /// On macOS, this function is always a no-op. + #[inline] pub fn suggest_fallback_scale_factor(&self, scale_factor: f64) -> Result<(), Error> { self.window_handle.suggest_scale_factor(scale_factor)?; Ok(()) @@ -60,24 +66,50 @@ impl WindowHandle { /// this call. /// /// Calling this method is more explicit, but otherwise identical to just dropping this [`WindowHandle`]. + #[inline] pub fn close(self) { drop(self) } /// Returns `true` if the window is still open, and returns `false` /// if the window was closed/dropped. + #[inline] pub fn is_open(&self) -> bool { self.window_handle.is_open() } + + /// Performs the work the window thread had scheduled for the main thread. + /// + /// This must be called back on the main thread, as a response to [`HostMainThreadCaller::call_main_thread`](host::HostMainThreadCaller::call_main_thread). + /// + /// # Platform compatibility notes + /// + /// Only the X11 platform has a separate window thread, so this is only needed to run host callbacks on X11. + /// + /// On Windows and macOS, this is always a no-op. + #[inline] + pub fn host_main_thread_callback(&mut self) { + self.window_handle.handle_main_thread_callback() + } } +#[inline] pub fn create_window( builder: WindowOpenOptions, handler: impl FnOnce(WindowContext) -> Result + Send + 'static, +) -> Result { + create_window_with_host(builder, handler, None) +} + +pub fn create_window_with_host( + builder: WindowOpenOptions, + handler: impl FnOnce(WindowContext) -> Result + Send + 'static, + host: impl Into>, ) -> Result { Ok(WindowHandle::new(platform::WindowHandle::create_window( builder, WindowHandlerBuilder::new(handler), + host.into().unwrap_or_else(Host::default), )?)) } diff --git a/src/window_open_options.rs b/src/window_open_options.rs index 9d9b48de..64c9d17d 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -41,7 +41,11 @@ impl WindowOpenOptions { } #[inline] - pub fn with_parent(mut self, parent: &impl HasWindowHandle) -> Self { + pub fn with_parent<'a, P: HasWindowHandle + 'a>( + mut self, parent: impl Into>, + ) -> Self { + let Some(parent) = parent.into() else { return self }; + let parent = match ParentWindowHandle::extract(parent) { Ok(parent) => parent, Err(e) => { diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index 0ee889b5..d80f94b1 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -152,7 +152,7 @@ pub trait ViewImpl: Sized { fn become_first_responder(this: ViewRef) -> bool; fn resign_first_responder(this: ViewRef) -> bool; fn window_should_close(this: ViewRef) -> bool; - fn view_did_change_backing_properties(this: ViewRef); + fn view_did_change_backing_properties(this: ViewRef, from_host: bool); fn hit_test(this: ViewRef<'_, Self>, point: NSPoint) -> Option<&NSView>; fn view_will_move_to_window(this: ViewRef, new_window: Option<&NSWindow>); fn update_tracking_areas(this: ViewRef); diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index 823a4c1d..6b7217fb 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -187,7 +187,7 @@ extern "C-unwind" fn view_did_change_backing_properties( this: &View, _: Sel, _: &AnyObject, ) { let Some(inner) = this.inner_ref() else { return }; - V::view_did_change_backing_properties(inner); + V::view_did_change_backing_properties(inner, true); } extern "C-unwind" fn hit_test(