diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index ccfc72cf..8eedd262 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -1,7 +1,6 @@ use baseview::dpi::LogicalSize; use baseview::{ - Event, EventStatus, Window, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, WindowSize, }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -24,10 +23,10 @@ impl ParentWindowHandler { let window_open_options = WindowOpenOptions::new() .with_size(LogicalSize::new(256, 256)) + .with_parent(&window) .with_title("baseview child"); - let child_window = - Window::open_parented(&window, window_open_options, ChildWindowHandler::new); + let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new); Self { surface: surface.into(), damaged: true.into(), _child_window: Some(child_window) } } @@ -122,5 +121,5 @@ impl WindowHandler for ChildWindowHandler { fn main() { let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); - Window::open_blocking(window_open_options, ParentWindowHandler::new); + baseview::create_window(window_open_options, ParentWindowHandler::new).run_until_closed(); } diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index ee68627b..adae5fe5 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -8,8 +8,7 @@ use rtrb::{Consumer, RingBuffer}; use baseview::copy_to_clipboard; use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::{ - Event, EventStatus, MouseEvent, Window, WindowContext, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, }; #[derive(Debug, Clone)] @@ -148,7 +147,7 @@ fn main() { } }); - Window::open_blocking(window_open_options, |window| { + baseview::create_window(window_open_options, |window| { let ctx = softbuffer::Context::new(window.clone()).unwrap(); let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); let size = window.size().physical; @@ -164,7 +163,8 @@ fn main() { is_cursor_inside: false.into(), damaged: true.into(), } - }); + }) + .run_until_closed(); } fn log_event(event: &Event) { diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 69904f64..102b1f4c 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -1,12 +1,12 @@ use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::gl::{GlConfig, GlContext}; use baseview::{ - Event, EventStatus, MouseEvent, Window, WindowContext, WindowHandler, WindowOpenOptions, - WindowSize, + Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, }; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; use std::cell::{Cell, RefCell}; +use std::ffi::CString; struct FemtovgExample { window_context: WindowContext, @@ -21,8 +21,10 @@ impl FemtovgExample { let gl_context = window_context.gl_context().unwrap(); unsafe { gl_context.make_current() }; - let renderer = - unsafe { OpenGl::new_from_function(|s| gl_context.get_proc_address(s)) }.unwrap(); + let renderer = unsafe { + OpenGl::new_from_function(|s| gl_context.get_proc_address(&CString::new(s).unwrap())) + } + .unwrap(); let mut canvas = Canvas::new(renderer).unwrap(); let size = window_context.size(); @@ -117,7 +119,7 @@ fn main() { .with_size(LogicalSize::new(512, 512)) .with_gl_config(GlConfig { alpha_bits: 8, ..GlConfig::default() }); - Window::open_blocking(window_open_options, FemtovgExample::new); + baseview::create_window(window_open_options, FemtovgExample::new).run_until_closed(); } fn log_event(event: &Event) { diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 043e1c47..0b5ca20a 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -1,7 +1,5 @@ use baseview::dpi::{LogicalSize, PhysicalSize}; -use baseview::{ - Event, EventStatus, Window, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, -}; +use baseview::{Event, EventStatus, WindowContext, WindowHandler, WindowOpenOptions, WindowSize}; use log::LevelFilter; use std::cell::RefCell; @@ -210,7 +208,8 @@ fn main() { .with_title("WGPU on Baseview") .with_size(LogicalSize::new(512, 512)); - Window::open_blocking(window_open_options, |c| pollster::block_on(WgpuExample::new(c))); + baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c))) + .run_until_closed(); } fn log_event(event: &Event) { diff --git a/src/gl.rs b/src/gl.rs index 4b020535..edfc89f7 100644 --- a/src/gl.rs +++ b/src/gl.rs @@ -1,5 +1,5 @@ use crate::platform::gl::*; -use std::ffi::c_void; +use std::ffi::{c_void, CStr}; use std::marker::PhantomData; #[derive(Clone, Debug, PartialEq)] @@ -70,7 +70,7 @@ impl GlContext { self.inner.make_not_current(); } - pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { self.inner.get_proc_address(symbol) } diff --git a/src/handler.rs b/src/handler.rs index a3ee7087..39b12d0b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,24 @@ -use crate::{Event, EventStatus, WindowSize}; +use super::*; pub trait WindowHandler: 'static { fn on_frame(&self); fn resized(&self, new_size: WindowSize); fn on_event(&self, event: Event) -> EventStatus; } + +#[allow(unused)] +pub struct WindowHandlerBuilder { + inner: Box Box + Send + 'static>, +} + +impl WindowHandlerBuilder { + pub fn new( + f: impl FnOnce(WindowContext) -> H + Send + 'static, + ) -> WindowHandlerBuilder { + Self { inner: Box::new(|c| Box::new(f(c))) } + } + + pub fn build(self, ctx: WindowContext) -> Box { + (self.inner)(ctx) + } +} diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index 2961a4df..a8cfe27a 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -29,7 +29,8 @@ impl WindowContext { pub fn request_close(&self) { let Some(view) = self.view.load() else { return }; - BaseviewView::close(view.inner_ref()); + let Some(view) = view.inner_ref() else { return }; + BaseviewView::close(view); } pub fn has_focus(&self) -> bool { @@ -58,7 +59,7 @@ impl WindowContext { pub fn resize(&self, size: Size) { let Some(view) = self.view.load() else { return }; - let view = view.inner_ref(); + let Some(view) = view.inner_ref() else { return }; if view.inner.state.closed.get() { return; } @@ -82,7 +83,7 @@ impl WindowContext { #[cfg(feature = "opengl")] pub fn gl_context(&self) -> Option { - Some(crate::gl::GlContext::new(self.view.load()?.inner().gl_context.get()?.clone())) + Some(crate::gl::GlContext::new(self.view.load()?.inner()?.gl_context.get()?.clone())) } pub fn window_handle(&self) -> Option> { diff --git a/src/platform/macos/gl.rs b/src/platform/macos/gl.rs index ff9b39fd..4d3a5f63 100644 --- a/src/platform/macos/gl.rs +++ b/src/platform/macos/gl.rs @@ -11,9 +11,9 @@ use objc2_app_kit::{ NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core, NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView, }; -use objc2_core_foundation::{CFBundle, CFString}; +use objc2_core_foundation::{CFBundle, CFString, CFStringBuiltInEncodings}; use objc2_foundation::NSSize; -use std::ffi::c_void; +use std::ffi::{c_void, CStr}; use std::ptr::NonNull; pub type CreationFailedError = (); @@ -103,8 +103,19 @@ impl GlContext { NSOpenGLContext::clearCurrentContext(); } - pub fn get_proc_address(&self, symbol: &str) -> *const c_void { - let symbol_name = CFString::from_str(symbol); + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { + // SAFETY: The string pointer is valid + let symbol_name = unsafe { + CFString::with_bytes( + None, + symbol.as_ptr().cast(), + symbol.count_bytes().try_into().unwrap(), + CFStringBuiltInEncodings::EncodingUTF8.0, + false, + ) + } + .unwrap(); + let framework_name = CFString::from_static_str("com.apple.opengl"); let framework = CFBundle::bundle_with_identifier(Some(&framework_name)).unwrap(); diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 556526e9..84af49f0 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -5,12 +5,14 @@ mod view; mod window; use crate::platform::macos::view::BaseviewView; -use crate::wrappers::appkit::View; +use crate::wrappers::appkit::{extract_raw_window_handle, View}; pub use context::WindowContext; use dispatch2::MainThreadBound; +use objc2::__framework_prelude::Retained; use objc2::rc::Weak; use objc2::MainThreadMarker; -use raw_window_handle::DisplayHandle; +use objc2_app_kit::NSView; +use raw_window_handle::{DisplayHandle, HasWindowHandle}; use std::fmt; use std::fmt::Formatter; pub use window::*; @@ -63,3 +65,14 @@ impl fmt::Debug for PlatformHandle { f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(self)).finish() } } + +#[derive(Debug, Clone, PartialEq)] +pub struct ParentWindowHandle { + view: Retained, +} + +impl ParentWindowHandle { + pub fn extract(window: &impl HasWindowHandle) -> Self { + Self { view: extract_raw_window_handle(window.window_handle().unwrap()).unwrap() } + } +} diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index f7a21b4e..b13f601d 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -2,6 +2,7 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; +use crate::handler::WindowHandlerBuilder; use crate::platform::macos::context::WindowContext; use crate::wrappers::appkit::*; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; @@ -19,7 +20,7 @@ use objc2_app_kit::{ NSTrackingAreaOptions, NSView, NSWindow, }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; -use std::cell::{Cell, OnceCell}; +use std::cell::{Cell, RefCell}; use std::rc::Rc; pub enum ViewParentingType { @@ -30,7 +31,7 @@ pub enum ViewParentingType { pub(crate) struct BaseviewView { pub(crate) state: Rc, pub(crate) mtm: MainThreadMarker, - window_handler: OnceCell>, + window_handler: WindowHandlerContainer, frame_timer: Cell>, notification_center_observer: Cell>, @@ -40,14 +41,13 @@ pub(crate) struct BaseviewView { parenting: ViewParentingType, #[cfg(feature = "opengl")] - pub(crate) gl_context: OnceCell, + pub(crate) gl_context: std::cell::OnceCell, } impl BaseviewView { - pub fn new( - _options: WindowOpenOptions, - builder: impl FnOnce(crate::WindowContext) -> H + Send + 'static, - parenting: ViewParentingType, final_size: LogicalSize, mtm: MainThreadMarker, + pub fn new( + _options: WindowOpenOptions, builder: WindowHandlerBuilder, parenting: ViewParentingType, + final_size: LogicalSize, mtm: MainThreadMarker, ) -> (Retained>, Rc) { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); @@ -60,12 +60,12 @@ impl BaseviewView { keyboard_state: KeyboardState::new(), frame_timer: None.into(), - window_handler: OnceCell::new(), + window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), parenting, #[cfg(feature = "opengl")] - gl_context: OnceCell::new(), + gl_context: std::cell::OnceCell::new(), }; let view = View::new(view_rect, inner, |view| { @@ -92,10 +92,10 @@ impl BaseviewView { } let context = WindowContext::new(view); - let handler = Box::new(builder(crate::WindowContext::new(context))); + let handler = builder.build(crate::WindowContext::new(context)); // Initialize handler - let Ok(()) = view.window_handler.set(handler) else { unreachable!() }; + view.window_handler.set(handler); // Set up anything that might trigger events to the handler @@ -106,14 +106,18 @@ impl BaseviewView { let timer_view = Weak::new(view.view); view.frame_timer.set(TimerHandle::new(0.015, move || { if let Some(view) = timer_view.load() { - Self::trigger_frame(view.inner_ref()); + if let Some(view) = view.inner_ref() { + Self::trigger_frame(view); + } } })); let notifier_view = Weak::new(view.view); let observer = NotificationCenterObserver::register_window_key_change(move |n| { if let Some(view) = notifier_view.load() { - BaseviewView::handle_notification(view.inner_ref(), n); + if let Some(view) = view.inner_ref() { + BaseviewView::handle_notification(view, n); + } } }); view.notification_center_observer.set(Some(observer)); @@ -125,6 +129,9 @@ impl BaseviewView { pub fn close(this: ViewRef) { this.state.closed.set(true); this.view.removeFromSuperview(); + this.notification_center_observer.take(); + this.frame_timer.take(); + this.window_handler.destroy(); if let ViewParentingType::Windowed { owned_window: parent_window, running_app } = &this.parenting @@ -167,17 +174,11 @@ impl BaseviewView { /// Trigger the event immediately and return the event status. fn trigger_event(this: ViewRef, event: Event) -> EventStatus { - let Some(handler) = this.window_handler.get() else { - return EventStatus::Ignored; - }; - - handler.on_event(event) + this.window_handler.use_handler(|h| h.on_event(event)).unwrap_or(EventStatus::Ignored) } fn trigger_frame(this: ViewRef) { - let Some(handler) = this.window_handler.get() else { return }; - - handler.on_frame(); + this.window_handler.use_handler(|h| h.on_frame()); } } @@ -224,9 +225,9 @@ impl ViewImpl for BaseviewView { this.state.size.set(current_size); this.state.scale_factor.set(current_scale_factor); - if let Some(handler) = this.window_handler.get() { - handler.resized(WindowSize::from_logical(current_size, current_scale_factor)) - } + this.window_handler.use_handler(|h| { + h.resized(WindowSize::from_logical(current_size, current_scale_factor)) + }); } } @@ -615,3 +616,41 @@ fn on_event(this: ViewRef, event: MouseEvent) -> NSDragOperation { _ => NSDragOperation::None, } } + +pub struct WindowHandlerContainer { + inner: RefCell>>, + must_be_destroyed: Cell, +} + +impl WindowHandlerContainer { + pub fn new() -> WindowHandlerContainer { + Self { inner: RefCell::new(None), must_be_destroyed: false.into() } + } + + pub fn use_handler(&self, user: impl FnOnce(&dyn WindowHandler) -> T) -> Option { + let returned = { + let inner = self.inner.try_borrow().ok()?; + user(inner.as_ref()?.as_ref()) + }; + + if self.must_be_destroyed.get() { + if let Ok(mut inner) = self.inner.try_borrow_mut() { + *inner = None; + } + } + + Some(returned) + } + + pub fn set(&self, handler: Box) { + self.inner.replace(Some(handler)); + self.must_be_destroyed.set(false); + } + + pub fn destroy(&self) { + match self.inner.try_borrow_mut() { + Ok(mut inner) => *inner = None, + Err(_) => self.must_be_destroyed.set(true), + } + } +} diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index e8805eaa..6d343005 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,103 +1,111 @@ use dpi::LogicalSize; -use objc2::rc::{autoreleasepool, Weak}; +use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; -use objc2_app_kit::{ - NSApplication, NSApplicationActivationPolicy, NSPasteboard, NSPasteboardTypeString, -}; +use objc2_app_kit::{NSApplication, NSPasteboard, NSPasteboardTypeString, NSView, NSWindow}; use objc2_foundation::{NSSize, NSString}; -use raw_window_handle::HasWindowHandle; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::rc::Rc; +use crate::handler::WindowHandlerBuilder; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; -use crate::wrappers::appkit::{create_window, extract_raw_window_handle, View}; -use crate::{WindowContext, WindowHandler, WindowOpenOptions}; +use crate::wrappers::appkit::{create_window, View}; +use crate::*; pub struct WindowHandle { - view: RefCell>>>, + mtm: MainThreadMarker, + view: Weak>, + _window: Option>, state: Rc, } -impl WindowHandle { - pub fn close(&self) { - let Some(view) = self.view.take().and_then(|w| w.load()) else { - return; - }; +impl Drop for WindowHandle { + fn drop(&mut self) { + let Some(view) = self.view.load() else { return }; + let Some(view) = view.inner_ref() else { return }; - BaseviewView::close(view.inner_ref()); - } - - pub fn is_open(&self) -> bool { - self.state.closed.get() + BaseviewView::close(view); } } -pub struct Window; - -impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { +impl WindowHandle { + pub fn create_window(mut options: WindowOpenOptions, handler: WindowHandlerBuilder) -> Self { autoreleasepool(|_| { - let Some(parent_view) = extract_raw_window_handle(parent.window_handle().unwrap()) - else { - panic!("Invalid window handle: ns_view is NULL"); - }; - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") + panic!("macOS: Windows can only be created on the main thread!") }; - let parenting = - ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; - - let backing_scale_factor = - parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); - let final_size = options.size.to_logical(backing_scale_factor); + // Creates the global NSApplication instance, if it doesn't exist yet + let _ = NSApplication::sharedApplication(mtm); - let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size, mtm); + if let Some(parent) = options.parent.take() { + return Self::create_window_parented(options, handler, parent.view, mtm); + } - WindowHandle { view: Some(Weak::from_retained(&ns_view)).into(), state } + Self::create_window_standalone(options, handler, mtm) }) } - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - autoreleasepool(|_| { - let Some(mtm) = MainThreadMarker::new() else { - panic!("macOS: open_blocking can only be called on the main thread!") - }; + pub fn create_window_parented( + builder: WindowOpenOptions, handler: WindowHandlerBuilder, parent_view: Retained, + mtm: MainThreadMarker, + ) -> Self { + let parenting = + ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; - // Creates the global NSApplication instance, if it doesn't exist yet - let app = NSApplication::sharedApplication(mtm); + let backing_scale_factor = + parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); + let final_size = builder.size.to_logical(backing_scale_factor); - let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + let (ns_view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); - let initial_size = options.size.to_logical(1.0); - let window = create_window(initial_size, mtm); - window.center(); + Self { mtm, state, _window: None, view: Weak::from_retained(&ns_view) } + } - let final_size = options.size.to_logical(window.backingScaleFactor()); - if final_size != initial_size { - window.setContentSize(NSSize::new(final_size.width, final_size.height)); - } + pub fn create_window_standalone( + builder: WindowOpenOptions, handler: WindowHandlerBuilder, mtm: MainThreadMarker, + ) -> Self { + let app = NSApplication::sharedApplication(mtm); + let window = create_window_with_options(&builder, mtm); - let title = NSString::from_str(&options.title); - window.setTitle(&title); - window.makeKeyAndOrderFront(None); + let final_size = window.contentRectForFrameRect(window.frame()).size; + let final_size = LogicalSize::new(final_size.width, final_size.height); - let parenting = ViewParentingType::Windowed { - running_app: Weak::from_retained(&app), - owned_window: Weak::from_retained(&window), - }; + let parenting = ViewParentingType::Windowed { + running_app: Weak::from_retained(&app), + owned_window: Weak::from_retained(&window), + }; - let _ = BaseviewView::new(options, build, parenting, final_size, mtm); + let (view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); - app.run(); - }) + Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) } } + + pub fn run_until_closed(self) { + NSApplication::sharedApplication(self.mtm).run(); + } + + pub fn is_open(&self) -> bool { + self.state.closed.get() + } +} + +fn create_window_with_options( + options: &WindowOpenOptions, mtm: MainThreadMarker, +) -> Retained { + let initial_size = options.size.to_logical(1.0); + let window = create_window(initial_size, mtm); + window.center(); + + let final_size = options.size.to_logical(window.backingScaleFactor()); + if final_size != initial_size { + window.setContentSize(NSSize::new(final_size.width, final_size.height)); + } + + let title = NSString::from_str(&options.title); + window.setTitle(&title); + + window.makeKeyAndOrderFront(None); + window } pub(crate) struct WindowSharedState { diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 3cc0585a..3e9b7a6f 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_void, CString, OsStr}; +use std::ffi::{c_void, CStr, OsStr}; use std::os::windows::ffi::OsStrExt; use std::rc::Rc; use windows_sys::{ @@ -307,8 +307,7 @@ impl GlContextInner { wglMakeCurrent(self.hdc, std::ptr::null_mut()); } - pub fn get_proc_address(&self, symbol: &str) -> *const c_void { - let symbol = CString::new(symbol).unwrap(); + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { let symbol_ptr = symbol.as_ptr().cast(); let addr = unsafe { diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index 3d75c41c..f75b0d70 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -5,7 +5,8 @@ mod window; mod window_state; use crate::wrappers::win32::h_instance::HInstance; -use raw_window_handle::{DisplayHandle, Win32WindowHandle}; +use crate::wrappers::win32::window::HWnd; +use raw_window_handle::{DisplayHandle, HasWindowHandle, RawWindowHandle, Win32WindowHandle}; use std::fmt::Debug; use std::num::NonZeroIsize; use std::rc::Rc; @@ -42,3 +43,19 @@ impl Debug for PlatformHandle { .finish() } } + +#[derive(Debug, Clone, PartialEq)] +pub struct ParentWindowHandle { + handle: HWnd, +} + +impl ParentWindowHandle { + pub fn extract(parent: &impl HasWindowHandle) -> Self { + let parent = match parent.window_handle().unwrap().as_raw() { + RawWindowHandle::Win32(h) => h.hwnd, + h => panic!("unsupported parent handle {:?}", h), + }; + + Self { handle: unsafe { HWnd::from_raw(parent.get() as _) } } + } +} diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index e45d2f4d..83749fbe 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -1,40 +1,38 @@ use windows_core::{ComObject, Result, HSTRING}; use windows_sys::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM}, + Foundation::{LPARAM, LRESULT, RECT, WPARAM}, UI::{ Controls::WM_MOUSELEAVE, WindowsAndMessaging::{ - PostMessageW, HTCLIENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_DPICHANGED, - WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, - WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SIZE, WM_SYSCHAR, - WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, + HTCLIENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_DPICHANGED, WM_INPUTLANGCHANGE, + WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, + WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, }, }, }; use dpi::{PhysicalPosition, PhysicalSize, Size}; -use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::cell::Cell; use std::num::NonZeroUsize; -use std::ptr::null_mut; pub(crate) const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; use super::drop_target::DropTarget; use super::*; +use crate::handler::WindowHandlerBuilder; use crate::platform::win::window_state::WindowState; use crate::wrappers::win32::cursor::SystemCursor; -use crate::{ - Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowOpenOptions, - WindowScalePolicy, WindowSize, -}; - use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessContext, ExtendedUser32, Rect, WindowStyle, }; +use crate::{ + Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowOpenOptions, WindowScalePolicy, + WindowSize, +}; #[allow(non_snake_case)] fn HIWORD(wparam: WPARAM) -> u16 { @@ -52,17 +50,13 @@ const WIN_FRAME_TIMER: NonZeroUsize = match NonZeroUsize::new(4242) { }; pub struct WindowHandle { - hwnd: Cell>, + hwnd: Cell>, is_open: Rc>, } impl WindowHandle { - pub fn close(&self) { - if let Some(hwnd) = self.hwnd.take() { - unsafe { - PostMessageW(hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); - } - } + pub fn run_until_closed(self) { + run_thread_message_loop_until(|| !self.is_open()).unwrap(); } pub fn is_open(&self) -> bool { @@ -70,6 +64,14 @@ impl WindowHandle { } } +impl Drop for WindowHandle { + fn drop(&mut self) { + if let Some(hwnd) = self.hwnd.take() { + let _ = hwnd.destroy(); + } + } +} + struct ParentHandle { is_open: Rc>, } @@ -80,13 +82,11 @@ impl Drop for ParentHandle { } } -type HandlerBuilder = dyn FnOnce(crate::WindowContext) -> Box; - pub struct BaseviewWindow { window_state: Rc, initial_size: Size, - handler_builder: Cell>>, + handler_builder: Cell>, // Things not directly used, but kept so their Drop impl runs when the window is destroyed _parent_handle: ParentHandle, @@ -145,7 +145,7 @@ impl WindowImpl for BaseviewWindow { let handler = { let context = crate::WindowContext::new(Rc::clone(&self.window_state)); - self.handler_builder.take().unwrap()(context) + self.handler_builder.take().unwrap().build(context) }; let Ok(()) = window_state.handler.set(handler) else { unreachable!() }; @@ -406,34 +406,8 @@ unsafe fn wnd_proc_inner( } } -pub struct Window; - -impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl for<'a> FnOnce(crate::WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - let parent = match parent.window_handle().unwrap().as_raw() { - RawWindowHandle::Win32(h) => h.hwnd, - h => panic!("unsupported parent handle {:?}", h), - }; - - Self::open(true, parent.get() as *mut _, options, build) - } - - pub fn open_blocking( - options: WindowOpenOptions, - build: impl for<'a> FnOnce(crate::WindowContext) -> H + Send + 'static, - ) { - let window_handle = Self::open(false, null_mut(), options, build); - - run_thread_message_loop_until(|| !window_handle.is_open()).unwrap(); - } - - fn open( - parented: bool, parent: HWND, options: WindowOpenOptions, - build: impl for<'a> FnOnce(crate::WindowContext) -> H + Send + 'static, - ) -> WindowHandle { +impl WindowHandle { + pub fn create_window(options: WindowOpenOptions, build: WindowHandlerBuilder) -> WindowHandle { let extended_user_32 = ExtendedUser32::load().unwrap(); let title = HSTRING::from(options.title); @@ -444,7 +418,11 @@ impl Window { let window_size = options.size.to_physical(scaling_factor); - let style = if parented { WindowStyle::parented() } else { WindowStyle::embedded() }; + let style = if options.parent.is_some() { + WindowStyle::parented() + } else { + WindowStyle::embedded() + }; let dpi_ctx = DpiAwarenessContext::new(&extended_user_32).unwrap(); let rect = @@ -467,7 +445,7 @@ impl Window { BaseviewWindow { window_state, initial_size: options.size, - handler_builder: Cell::new(Some(Box::new(|w| Box::new(build(w))))), + handler_builder: Cell::new(Some(build)), _parent_handle: parent_handle, _drop_target: None.into(), @@ -479,9 +457,10 @@ impl Window { } }; + let parent = options.parent.map(|p| p.handle); + let hwnd = - create_window(&title, style, rect.size(), parent as *mut _, &dpi_ctx, initializer) - .unwrap(); + create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer).unwrap(); // SAFETY: this handle should be safe to use let window = unsafe { HWnd::from_raw(hwnd) }; @@ -495,7 +474,7 @@ impl Window { window.show_and_activate(); - WindowHandle { hwnd: Some(hwnd).into(), is_open: Rc::clone(&is_open) } + WindowHandle { hwnd: Some(window).into(), is_open: Rc::clone(&is_open) } } } diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index 84405467..7e16ad0f 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -401,7 +401,7 @@ impl DragNDropState { }; // Ignore if this was meant for another window (?) - if event.requestor != window.window_id.get() { + if event.requestor != window.xcb_window.id().get() { return Ok(()); } @@ -486,7 +486,7 @@ fn send_status_rejected( response_type: xproto::CLIENT_MESSAGE_EVENT, window: source_window, format: 32, - data: [window.window_id.get(), 0, 0, 0, conn.atoms.None as _].into(), + data: [window.xcb_window.id().get(), 0, 0, 0, conn.atoms.None as _].into(), sequence: 0, type_: conn.atoms.XdndStatus, }; @@ -508,7 +508,7 @@ fn send_status_event( response_type: xproto::CLIENT_MESSAGE_EVENT, window: source_window, format: 32, - data: [window.window_id.get(), 1, 0, 0, action as _].into(), + data: [window.xcb_window.id().get(), 1, 0, 0, action as _].into(), sequence: 0, type_: conn.atoms.XdndStatus, }; @@ -527,7 +527,7 @@ pub fn send_finished_rejected( response_type: xproto::CLIENT_MESSAGE_EVENT, window: source_window, format: 32, - data: [window.window_id.get(), 1, window.connection.atoms.None as _, 0, 0].into(), + data: [window.xcb_window.id().get(), 1, window.connection.atoms.None as _, 0, 0].into(), sequence: 0, type_: conn.atoms.XdndFinished as _, }; @@ -548,7 +548,7 @@ fn send_finished_event( response_type: xproto::CLIENT_MESSAGE_EVENT, window: source_window, format: 32, - data: [window.window_id.get(), 1, action as _, 0, 0].into(), + data: [window.xcb_window.id().get(), 1, action as _, 0, 0].into(), sequence: 0, type_: conn.atoms.XdndFinished as _, }; @@ -564,7 +564,7 @@ fn request_convert_selection( let conn = &window.connection; conn.conn.convert_selection( - window.window_id.get(), + window.xcb_window.id().get(), conn.atoms.XdndSelection, conn.atoms.TextUriList, conn.atoms.XdndSelection, @@ -585,14 +585,14 @@ fn translate_root_coordinates( let x = x.try_into().unwrap_or(i16::MAX); let y = y.try_into().unwrap_or(i16::MAX); - if root_id == window.window_id.get() { + if root_id == window.xcb_window.id().get() { return Ok(PhysicalPosition::new(x, y)); } let reply = window .connection .conn - .translate_coordinates(root_id, window.window_id.get(), x, y)? + .translate_coordinates(root_id, window.xcb_window.id().get(), x, y)? .reply()?; Ok(PhysicalPosition::new(reply.dst_x, reply.dst_y)) @@ -602,7 +602,7 @@ fn fetch_dnd_data(window: &WindowInner) -> Result> { let conn = &window.connection; let data: Vec = conn.get_property( - window.window_id.get(), + window.xcb_window.id().get(), conn.atoms.XdndSelection, conn.atoms.TextUriList, )?; diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 63163f9f..34139729 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -28,12 +28,12 @@ pub(crate) struct EventLoop { impl EventLoop { pub fn new( - window: Rc, handler: impl WindowHandler, parent_handle: Option, - xkb_state: Option, + window: Rc, handler: Box, + parent_handle: Option, xkb_state: Option, ) -> Self { Self { window, - handler: Box::new(handler), + handler, parent_handle, frame_interval: Duration::from_millis(15), event_loop_running: false, diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index b874b5af..11b7adaa 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -3,8 +3,8 @@ use crate::gl::*; use crate::wrappers::glx::*; use crate::wrappers::xlib::{XErrorHandler, XLibError}; -use std::ffi::{c_void, CString}; -use std::os::raw::c_ulong; +use crate::platform::x11::xcb_window::XcbWindow; +use std::ffi::{c_ulong, c_void, CStr}; use std::rc::Rc; use x11_dl::error::OpenError; use x11_dl::glx::GLXContext; @@ -36,7 +36,7 @@ pub type GlContext = Rc; pub struct GlContextInner { glx: Glx, - window: c_ulong, + window: NonZeroU32, connection: Rc, context: GLXContext, } @@ -65,8 +65,8 @@ impl GlContextInner { /// /// Use [Self::get_fb_config_and_visual] to create both of these things. pub fn create( - window: c_ulong, connection: Rc, config: FbConfig, - ) -> Result { + window: &XcbWindow, connection: Rc, config: FbConfig, + ) -> Result, GlError> { let glx = Glx::open()?; let xlib_connection = connection.conn.xlib_connection(); @@ -87,20 +87,25 @@ impl GlContextInner { error_handler, )?; + let window_id = window.id().get().into(); // Create context object here so that error or panic will properly free the context - let context = - GlContextInner { glx, window, connection: Rc::clone(&connection), context }; + let context = GlContextInner { + glx, + window: window.id(), + connection: Rc::clone(&connection), + context, + }; unsafe { context.glx.with_current_context( xlib_connection, - window, + window_id, context.context, error_handler, || { swap_interval( xlib_connection.as_raw(), - window, + window_id, config.gl_config.vsync as i32, ); error_handler.check() @@ -108,7 +113,7 @@ impl GlContextInner { )??; } - Ok(context) + Ok(Rc::new(context)) }) } @@ -142,7 +147,7 @@ impl GlContextInner { self.glx .make_current( self.connection.conn.xlib_connection(), - self.window, + self.window_id(), self.context, error_handler, ) @@ -156,10 +161,12 @@ impl GlContextInner { }) } - pub fn get_proc_address(&self, symbol: &str) -> *const c_void { - let symbol = CString::new(symbol).unwrap(); + fn window_id(&self) -> c_ulong { + self.window.get().into() + } - match self.glx.get_proc_address(&symbol) { + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { + match self.glx.get_proc_address(symbol) { Some(ptr) => ptr.as_ptr(), None => std::ptr::null(), } @@ -168,7 +175,11 @@ impl GlContextInner { pub fn swap_buffers(&self) { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx - .swap_buffers(self.connection.conn.xlib_connection(), self.window, error_handler) + .swap_buffers( + self.connection.conn.xlib_connection(), + self.window_id(), + error_handler, + ) .unwrap() }) } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 220fb807..9fe96882 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,8 +1,8 @@ mod xcb_connection; -use raw_window_handle::{DisplayHandle, XcbWindowHandle}; +use raw_window_handle::{DisplayHandle, HasWindowHandle, RawWindowHandle, XcbWindowHandle}; use std::fmt::Formatter; -use std::num::NonZero; +use std::num::{NonZero, NonZeroU32}; use std::rc::Rc; use std::sync::Arc; pub(crate) use xcb_connection::X11Connection; @@ -15,6 +15,7 @@ mod drag_n_drop; mod event_loop; mod keyboard; mod visual_info; +mod xcb_window; mod window_shared; @@ -56,3 +57,20 @@ impl std::fmt::Debug for PlatformHandle { .finish() } } + +#[derive(Debug, Clone, PartialEq)] +pub struct ParentWindowHandle { + window_id: NonZeroU32, +} + +impl ParentWindowHandle { + pub fn extract(window: &impl HasWindowHandle) -> Self { + let window_id = match window.window_handle().unwrap().as_raw() { + RawWindowHandle::Xlib(h) => NonZeroU32::new(h.window.try_into().unwrap()).unwrap(), + RawWindowHandle::Xcb(h) => h.window, + h => panic!("unsupported parent handle type {:?}", h), + }; + + Self { window_id } + } +} diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index 1b4c0501..d359a5b7 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -1,4 +1,3 @@ -use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::cell::Cell; use std::error::Error; use std::num::NonZero; @@ -9,16 +8,14 @@ use std::sync::Arc; use std::thread::{self, JoinHandle}; use x11rb::connection::Connection; -use x11rb::protocol::xproto::{ - AtomEnum, ConnectionExt, CreateGCAux, CreateWindowAux, EventMask, PropMode, WindowClass, -}; -use x11rb::wrapper::ConnectionExt as _; use super::X11Connection; use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; -use crate::context::WindowContext; +use crate::handler::WindowHandlerBuilder; use crate::platform::x11::window_shared::WindowInner; -use crate::{WindowHandler, WindowOpenOptions, WindowScalePolicy, WindowSize}; +use crate::platform::x11::xcb_window::XcbWindow; +use crate::wrappers::xkbcommon::XkbcommonState; +use crate::*; pub struct WindowHandle { window_id: Option>, @@ -28,11 +25,27 @@ pub struct WindowHandle { } impl WindowHandle { - pub fn close(&self) { - self.close_requested.store(true, Ordering::Relaxed); - if let Some(event_loop) = self.event_loop_handle.take() { - let _ = event_loop.join(); - } + pub fn create_window( + options: WindowOpenOptions, handler: WindowHandlerBuilder, + ) -> WindowHandle { + let (tx, rx) = mpsc::sync_channel::(1); + let (parent_handle, mut window_handle) = ParentHandle::new(); + let join_handle = thread::spawn(move || { + Window::window_thread(options, handler, tx.clone(), Some(parent_handle)).unwrap(); + }); + + let raw_window_handle = rx.recv().unwrap().unwrap(); + window_handle.window_id = Some(raw_window_handle); + window_handle.event_loop_handle = Some(join_handle).into(); + window_handle + } + + pub fn run_until_closed(self) { + let Some(thread) = self.event_loop_handle.take() else { return }; + + thread.join().unwrap_or_else(|err| { + eprintln!("Window thread panicked: {:#?}", err); + }); } pub fn is_open(&self) -> bool { @@ -40,6 +53,15 @@ impl WindowHandle { } } +impl Drop for WindowHandle { + fn drop(&mut self) { + self.close_requested.store(true, Ordering::Relaxed); + if let Some(event_loop) = self.event_loop_handle.take() { + let _ = event_loop.join(); + } + } +} + pub(crate) struct ParentHandle { close_requested: Arc, is_open: Arc, @@ -75,68 +97,15 @@ pub struct Window; type WindowOpenResult = Result, ()>; impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - // Convert parent into something that X understands - let parent_id = match parent.window_handle().unwrap().as_raw() { - RawWindowHandle::Xlib(h) => h.window as u32, - RawWindowHandle::Xcb(h) => h.window.get(), - h => panic!("unsupported parent handle type {:?}", h), - }; - - let (tx, rx) = mpsc::sync_channel::(1); - let (parent_handle, mut window_handle) = ParentHandle::new(); - let join_handle = thread::spawn(move || { - Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)) - .unwrap(); - }); - - let raw_window_handle = rx.recv().unwrap().unwrap(); - window_handle.window_id = Some(raw_window_handle); - window_handle.event_loop_handle = Some(join_handle).into(); - window_handle - } - - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - let (tx, rx) = mpsc::sync_channel::(1); - - let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx, None).unwrap(); - }); - - let _ = rx.recv().unwrap().unwrap(); - - thread.join().unwrap_or_else(|err| { - eprintln!("Window thread panicked: {:#?}", err); - }); - } - - fn window_thread( - parent: Option, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, + fn window_thread( + options: WindowOpenOptions, build: WindowHandlerBuilder, tx: mpsc::SyncSender, parent_handle: Option, ) -> Result<(), Box> { // Connect to the X server - // FIXME: baseview error type instead of unwrap() let xcb_connection = X11Connection::new()?; // Setup xkbcommon - let xkb_state = crate::wrappers::xkbcommon::XkbcommonState::new(&xcb_connection); - - // Get screen information - let screen = xcb_connection.screen(); - let parent_id = parent.unwrap_or(screen.root); - - let gc_id = xcb_connection.conn.generate_id()?; - xcb_connection.conn.create_gc( - gc_id, - parent_id, - &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), - )?; + let xkb_state = XkbcommonState::new(&xcb_connection); let scaling = match options.scale { WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), @@ -152,88 +121,33 @@ impl Window { #[cfg(not(feature = "opengl"))] let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; - let Some(window_id) = NonZero::new(xcb_connection.conn.generate_id()?) else { - unreachable!(); - }; - - xcb_connection.conn.create_window( - visual_info.visual_depth, - window_id.get(), - parent_id, - 0, // x coordinate of the new window - 0, // y coordinate of the new window - physical_size.width, // window width - physical_size.height, // window height - 0, // window border - WindowClass::INPUT_OUTPUT, - visual_info.visual_id, - &CreateWindowAux::new() - .event_mask( - EventMask::EXPOSURE - | EventMask::POINTER_MOTION - | EventMask::BUTTON_PRESS - | EventMask::BUTTON_RELEASE - | EventMask::KEY_PRESS - | EventMask::KEY_RELEASE - | EventMask::STRUCTURE_NOTIFY - | EventMask::ENTER_WINDOW - | EventMask::LEAVE_WINDOW - | EventMask::FOCUS_CHANGE, - ) - // As mentioned above, these two values are needed to be able to create a window - // with a depth of 32-bits when the parent window has a different depth - .colormap(visual_info.color_map) - .border_pixel(0), - )?; - xcb_connection.conn.map_window(window_id.get())?; - - // Change window title - let title = options.title; - xcb_connection.conn.change_property8( - PropMode::REPLACE, - window_id.get(), - AtomEnum::WM_NAME, - AtomEnum::STRING, - title.as_bytes(), - )?; + let xcb_connection = Rc::new(xcb_connection); - xcb_connection.conn.change_property32( - PropMode::REPLACE, - window_id.get(), - xcb_connection.atoms.WM_PROTOCOLS, - AtomEnum::ATOM, - &[xcb_connection.atoms.WM_DELETE_WINDOW], + let x_window = XcbWindow::new( + Rc::clone(&xcb_connection), + physical_size, + &visual_info, + options.parent.map(|p| p.window_id), )?; - // Enable drag and drop (TODO: Make this toggleable?) - xcb_connection.conn.change_property32( - PropMode::REPLACE, - window_id.get(), - xcb_connection.atoms.XdndAware, - AtomEnum::ATOM, - &[5u32], // Latest version; hasn't changed since 2002 - )?; + x_window.map_window()?; + x_window.set_title(&options.title)?; + x_window.enable_wm_protocols()?; + x_window.enable_dnd_protocols()?; xcb_connection.conn.flush()?; - let xcb_connection = Rc::new(xcb_connection); #[cfg(feature = "opengl")] let gl_context = visual_info.fb_config.map(|fb_config| { - use std::ffi::c_ulong; - - let window = window_id.get() as c_ulong; - // Because of the visual negotation we had to take some extra steps to create this context - let context = - super::gl::GlContextInner::create(window, Rc::clone(&xcb_connection), fb_config) - .expect("Could not create OpenGL context"); - - Rc::new(context) + super::gl::GlContextInner::create(&x_window, Rc::clone(&xcb_connection), fb_config) + .expect("Could not create OpenGL context") }); + let window_id = x_window.id(); let inner = Rc::new(WindowInner::new( xcb_connection, - window_id, + x_window, physical_size, scaling, visual_info.visual_id.try_into()?, @@ -241,11 +155,7 @@ impl Window { gl_context, )); - let handler = build(WindowContext::new(Rc::clone(&inner))); - - // Send an initial window resized event so the user is alerted of - // the correct dpi scaling. - handler.resized(WindowSize::from_physical(physical_size.cast(), scaling)); + let handler = build.build(WindowContext::new(Rc::clone(&inner))); let _ = tx.send(Ok(window_id)); diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 672d4441..965f4599 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,3 +1,4 @@ +use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::X11Connection; use crate::{MouseCursor, WindowSize}; use dpi::{PhysicalSize, Size}; @@ -9,7 +10,6 @@ use std::sync::Arc; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt, InputFocus, Visualid, - Window as XWindow, }; use x11rb::CURRENT_TIME; @@ -18,8 +18,8 @@ pub(crate) struct WindowInner { #[cfg(feature = "opengl")] gl_context: Option, + pub(crate) xcb_window: XcbWindow, pub(crate) connection: Rc, - pub(crate) window_id: NonZero, pub(crate) scaling_factor: Cell, pub(crate) window_size: Cell>, mouse_cursor: Cell, @@ -31,13 +31,13 @@ pub(crate) struct WindowInner { impl WindowInner { pub(crate) fn new( - connection: Rc, window_id: NonZero, window_size: PhysicalSize, + connection: Rc, xcb_window: XcbWindow, window_size: PhysicalSize, scale_factor: f64, visual_id: NonZero, #[cfg(feature = "opengl")] gl_context: Option, ) -> Self { Self { connection, - window_id, + xcb_window, visual_id, window_size: window_size.into(), scaling_factor: scale_factor.into(), @@ -60,7 +60,7 @@ impl WindowInner { if xid != 0 { let _ = self.connection.conn.change_window_attributes( - self.window_id.get(), + self.xcb_window.id().get(), &ChangeWindowAttributesAux::new().cursor(xid), ); let _ = self.connection.conn.flush(); @@ -80,7 +80,7 @@ impl WindowInner { pub fn focus(&self) { let _ = self.connection.conn.set_input_focus( InputFocus::POINTER_ROOT, - self.window_id, + self.xcb_window.id(), CURRENT_TIME, ); let _ = self.connection.conn.flush(); @@ -90,7 +90,7 @@ impl WindowInner { let new_physical_size = size.to_physical::(self.scaling_factor.get()); let _ = self.connection.conn.configure_window( - self.window_id.get(), + self.xcb_window.id().get(), &ConfigureWindowAux::new() .width(new_physical_size.width) .height(new_physical_size.height), @@ -102,7 +102,7 @@ impl WindowInner { } pub fn window_handle(&self) -> Option> { - let mut handle = XlibWindowHandle::new(self.window_id.get() as _); + let mut handle = XlibWindowHandle::new(self.xcb_window.id().get() as _); handle.visual_id = self.visual_id.get().into(); Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) } @@ -114,7 +114,7 @@ impl WindowInner { pub fn platform_handle(&self) -> super::PlatformHandle { super::PlatformHandle { connection: Arc::clone(&self.connection.conn), - window_id: self.window_id, + window_id: self.xcb_window.id(), visual_id: self.visual_id, } } diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs new file mode 100644 index 00000000..7ff1d85b --- /dev/null +++ b/src/platform/x11/xcb_window.rs @@ -0,0 +1,107 @@ +use crate::platform::x11::visual_info::WindowVisualConfig; +use crate::platform::X11Connection; +use dpi::PhysicalSize; +use std::num::{NonZero, NonZeroU32}; +use std::rc::Rc; +use x11rb::connection::Connection; +use x11rb::cookie::VoidCookie; +use x11rb::errors::ReplyOrIdError; +use x11rb::protocol::xproto::{ + AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass, +}; +use x11rb::wrapper::ConnectionExt as _; +use x11rb::xcb_ffi::XCBConnection; + +pub struct XcbWindow { + connection: Rc, + window_id: NonZeroU32, +} + +impl XcbWindow { + pub fn new( + connection: Rc, size: PhysicalSize, visual_info: &WindowVisualConfig, + parent_id: Option, + ) -> Result { + let Some(window_id) = NonZero::new(connection.conn.generate_id()?) else { + unreachable!(); + }; + + connection.conn.create_window( + visual_info.visual_depth, + window_id.get(), + parent_id.map_or(connection.screen().root, NonZeroU32::get), + 0, // x coordinate of the new window + 0, // y coordinate of the new window + size.width, // window width + size.height, // window height + 0, // window border + WindowClass::INPUT_OUTPUT, + visual_info.visual_id, + &CreateWindowAux::new() + .event_mask( + EventMask::EXPOSURE + | EventMask::POINTER_MOTION + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::KEY_PRESS + | EventMask::KEY_RELEASE + | EventMask::STRUCTURE_NOTIFY + | EventMask::ENTER_WINDOW + | EventMask::LEAVE_WINDOW + | EventMask::FOCUS_CHANGE, + ) + // As mentioned above, these two values are needed to be able to create a window + // with a depth of 32-bits when the parent window has a different depth + .colormap(visual_info.color_map) + .border_pixel(0), + )?; + + Ok(Self { window_id, connection }) + } + + pub fn map_window(&self) -> Result, ReplyOrIdError> { + Ok(self.connection.conn.map_window(self.window_id.get())?) + } + + pub fn set_title(&self, title: &str) -> Result, ReplyOrIdError> { + Ok(self.connection.conn.change_property8( + PropMode::REPLACE, + self.window_id.get(), + AtomEnum::WM_NAME, + AtomEnum::STRING, + title.as_bytes(), + )?) + } + + pub fn enable_wm_protocols(&self) -> Result, ReplyOrIdError> { + Ok(self.connection.conn.change_property32( + PropMode::REPLACE, + self.window_id.get(), + self.connection.atoms.WM_PROTOCOLS, + AtomEnum::ATOM, + &[self.connection.atoms.WM_DELETE_WINDOW], + )?) + } + + pub fn enable_dnd_protocols(&self) -> Result, ReplyOrIdError> { + Ok(self.connection.conn.change_property32( + PropMode::REPLACE, + self.window_id.get(), + self.connection.atoms.XdndAware, + AtomEnum::ATOM, + &[5u32], // Latest version; hasn't changed since 2002 + )?) + } + + #[inline] + pub fn id(&self) -> NonZeroU32 { + self.window_id + } +} + +impl Drop for XcbWindow { + fn drop(&mut self) { + let Ok(cookie) = self.connection.conn.destroy_window(self.window_id.get()) else { return }; + let _ = cookie.check(); + } +} diff --git a/src/window.rs b/src/window.rs index 5334d35a..fc44bdc3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,9 +1,7 @@ -use crate::context::WindowContext; -use crate::handler::WindowHandler; +use crate::handler::WindowHandlerBuilder; use crate::platform; -use crate::window_open_options::WindowOpenOptions; +use crate::*; use dpi::{LogicalSize, PhysicalSize, Pixel}; -use raw_window_handle::HasWindowHandle; use std::marker::PhantomData; pub struct WindowHandle { @@ -17,9 +15,13 @@ impl WindowHandle { Self { window_handle, phantom: PhantomData } } + pub fn run_until_closed(self) { + self.window_handle.run_until_closed() + } + /// Close the window - pub fn close(&self) { - self.window_handle.close(); + pub fn close(self) { + drop(self) } /// Returns `true` if the window is still open, and returns `false` @@ -29,24 +31,13 @@ impl WindowHandle { } } -pub struct Window { - _private: (), -} - -impl Window { - pub fn open_parented( - parent: &impl HasWindowHandle, options: WindowOpenOptions, - build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) -> WindowHandle { - let window_handle = platform::Window::open_parented(parent, options, build); - WindowHandle::new(window_handle) - } - - pub fn open_blocking( - options: WindowOpenOptions, build: impl FnOnce(WindowContext) -> H + Send + 'static, - ) { - platform::Window::open_blocking(options, build) - } +pub fn create_window( + builder: WindowOpenOptions, handler: impl FnOnce(WindowContext) -> H + Send + 'static, +) -> WindowHandle { + WindowHandle::new(platform::WindowHandle::create_window( + builder, + WindowHandlerBuilder::new(handler), + )) } /// A window's size, which can be read in either logical or physical pixels. diff --git a/src/window_open_options.rs b/src/window_open_options.rs index e276f9d8..5d32db91 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -1,7 +1,8 @@ -use dpi::{LogicalSize, Size}; - #[cfg(feature = "opengl")] use crate::gl::GlConfig; +use crate::platform::ParentWindowHandle; +use dpi::{LogicalSize, Size}; +use raw_window_handle::HasWindowHandle; /// The dpi scaling policy of the window #[derive(Default, Debug, Clone, Copy, PartialEq)] @@ -25,6 +26,8 @@ pub struct WindowOpenOptions { /// The dpi scaling policy pub scale: WindowScalePolicy, + pub(crate) parent: Option, + /// If provided, then an OpenGL context will be created for this window. You'll be able to /// access this context through [crate::WindowContext::gl_context]. /// @@ -57,6 +60,12 @@ impl WindowOpenOptions { self } + #[inline] + pub fn with_parent(mut self, parent: &impl HasWindowHandle) -> Self { + self.parent = Some(ParentWindowHandle::extract(parent)); + self + } + #[cfg(feature = "opengl")] #[inline] pub fn with_gl_config(mut self, gl_config: impl Into>) -> Self { @@ -71,6 +80,7 @@ impl Default for WindowOpenOptions { title: String::from("baseview window"), size: LogicalSize { width: 500.0, height: 400.0 }.into(), scale: WindowScalePolicy::default(), + parent: None, #[cfg(feature = "opengl")] gl_config: None, } diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index c5a27e5b..4394b51e 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -51,7 +51,7 @@ impl View { let view: Retained> = unsafe { msg_send![view, initWithFrame: frame] }; - init(view.inner_ref()); + init(view.inner_ref().unwrap()); view } @@ -68,23 +68,28 @@ impl View { let ivar = class.instance_variable(BASEVIEW_STATE_IVAR).unwrap(); let ivar = unsafe { ivar.load_ptr::<*mut c_void>(this) }; let raw = unsafe { ivar.read() }; + + if raw.is_null() { + return; + } + let inner = unsafe { Box::>::from_raw(raw.cast()) }; unsafe { ivar.write(core::ptr::null_mut()) }; drop(inner); } - fn get_inner(&self) -> &ViewInner { + fn get_inner(&self) -> Option<&ViewInner> { let ivar = self.class().instance_variable(BASEVIEW_STATE_IVAR).unwrap(); let ivar = unsafe { ivar.load::<*mut c_void>(self) }; - unsafe { ivar.cast::>().as_ref() }.unwrap() + unsafe { ivar.cast::>().as_ref() } } - pub fn inner(&self) -> &V { - &self.get_inner().inner + pub fn inner(&self) -> Option<&V> { + Some(&self.get_inner()?.inner) } - pub fn inner_ref(&self) -> ViewRef<'_, V> { - ViewRef { view: self, inner: self.inner() } + pub fn inner_ref(&self) -> Option> { + Some(ViewRef { view: self, inner: self.inner()? }) } pub fn window_handle_from_weak(this: &Weak) -> Option> { diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index a3706768..96026124 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -162,125 +162,150 @@ extern "C-unwind" fn accepts_first_mouse(_this: &NSView, _sel: Sel, _event: &NSE } extern "C-unwind" fn become_first_responder(this: &View, _sel: Sel) -> Bool { - V::become_first_responder(this.inner_ref()).into() + let Some(inner) = this.inner_ref() else { return false.into() }; + V::become_first_responder(inner).into() } extern "C-unwind" fn resign_first_responder(this: &View, _sel: Sel) -> Bool { - V::resign_first_responder(this.inner_ref()).into() + let Some(inner) = this.inner_ref() else { return true.into() }; + V::resign_first_responder(inner).into() } extern "C-unwind" fn window_should_close( this: &View, _: Sel, _sender: &AnyObject, ) -> Bool { - V::window_should_close(this.inner_ref()).into() + let Some(inner) = this.inner_ref() else { return true.into() }; + V::window_should_close(inner).into() } extern "C-unwind" fn view_did_change_backing_properties( this: &View, _: Sel, _: &AnyObject, ) { - V::view_did_change_backing_properties(this.inner_ref()); + let Some(inner) = this.inner_ref() else { return }; + V::view_did_change_backing_properties(inner); } extern "C-unwind" fn hit_test( this: &View, _sel: Sel, point: NSPoint, ) -> Option<&NSView> { - V::hit_test(this.inner_ref(), point) + V::hit_test(this.inner_ref()?, point) } extern "C-unwind" fn view_will_move_to_window( this: &View, _self: Sel, new_window: Option<&NSWindow>, ) { - V::view_will_move_to_window(this.inner_ref(), new_window); + let Some(inner) = this.inner_ref() else { return }; + V::view_will_move_to_window(inner, new_window); } extern "C-unwind" fn update_tracking_areas(this: &View, _self: Sel, _: &AnyObject) { - V::update_tracking_areas(this.inner_ref()); + let Some(inner) = this.inner_ref() else { return }; + V::update_tracking_areas(inner); } extern "C-unwind" fn mouse_moved(this: &View, _sel: Sel, event: &NSEvent) { - V::mouse_moved(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::mouse_moved(inner, event); } extern "C-unwind" fn scroll_wheel(this: &View, _: Sel, event: &NSEvent) { - V::scroll_wheel(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::scroll_wheel(inner, event); } extern "C-unwind" fn dragging_entered( this: &View, _sel: Sel, sender: Option<&ProtocolObject>, ) -> NSDragOperation { - V::dragging_entered(this.inner_ref(), sender) + let Some(inner) = this.inner_ref() else { return NSDragOperation::None }; + V::dragging_entered(inner, sender) } extern "C-unwind" fn dragging_updated( this: &View, _sel: Sel, sender: Option<&ProtocolObject>, ) -> NSDragOperation { - V::dragging_updated(this.inner_ref(), sender) + let Some(inner) = this.inner_ref() else { return NSDragOperation::None }; + V::dragging_updated(inner, sender) } extern "C-unwind" fn prepare_for_drag_operation( this: &View, _sel: Sel, sender: Option<&ProtocolObject>, ) -> Bool { - V::prepare_for_drag_operation(this.inner_ref(), sender).into() + let Some(inner) = this.inner_ref() else { return false.into() }; + V::prepare_for_drag_operation(inner, sender).into() } extern "C-unwind" fn perform_drag_operation( this: &View, _sel: Sel, sender: Option<&ProtocolObject>, ) -> Bool { - V::perform_drag_operation(this.inner_ref(), sender).into() + let Some(inner) = this.inner_ref() else { return false.into() }; + V::perform_drag_operation(inner, sender).into() } extern "C-unwind" fn dragging_exited( this: &View, _sel: Sel, sender: Option<&ProtocolObject>, ) { - V::dragging_exited(this.inner_ref(), sender) + let Some(inner) = this.inner_ref() else { return }; + V::dragging_exited(inner, sender) } extern "C-unwind" fn handle_notification( this: &View, _cmd: Sel, notification: &NSNotification, ) { - V::handle_notification(this.inner_ref(), notification) + let Some(inner) = this.inner_ref() else { return }; + V::handle_notification(inner, notification) } extern "C-unwind" fn mouse_entered(this: &View, _: Sel, _: &AnyObject) { - V::mouse_entered(this.inner_ref()); + let Some(inner) = this.inner_ref() else { return }; + V::mouse_entered(inner); } extern "C-unwind" fn mouse_exited(this: &View, _: Sel, _: &AnyObject) { - V::mouse_exited(this.inner_ref()); + let Some(inner) = this.inner_ref() else { return }; + V::mouse_exited(inner); } extern "C-unwind" fn key_down(this: &View, _: Sel, event: &NSEvent) { - V::key_down(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::key_down(inner, event); } extern "C-unwind" fn key_up(this: &View, _: Sel, event: &NSEvent) { - V::key_up(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::key_up(inner, event); } extern "C-unwind" fn flags_changed(this: &View, _: Sel, event: &NSEvent) { - V::flags_changed(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::flags_changed(inner, event); } extern "C-unwind" fn mouse_down(this: &View, _sel: Sel, event: &NSEvent) { - V::mouse_down(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::mouse_down(inner, event); } extern "C-unwind" fn mouse_up(this: &View, _sel: Sel, event: &NSEvent) { - V::mouse_up(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::mouse_up(inner, event); } extern "C-unwind" fn right_mouse_down(this: &View, _sel: Sel, event: &NSEvent) { - V::right_mouse_down(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::right_mouse_down(inner, event); } extern "C-unwind" fn right_mouse_up(this: &View, _sel: Sel, event: &NSEvent) { - V::right_mouse_up(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::right_mouse_up(inner, event); } extern "C-unwind" fn other_mouse_down(this: &View, _sel: Sel, event: &NSEvent) { - V::other_mouse_down(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::other_mouse_down(inner, event); } extern "C-unwind" fn other_mouse_up(this: &View, _sel: Sel, event: &NSEvent) { - V::other_mouse_up(this.inner_ref(), event); + let Some(inner) = this.inner_ref() else { return }; + V::other_mouse_up(inner, event); } diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 94bd72a8..9dcea9c3 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -51,7 +51,7 @@ pub trait WindowImpl: 'static { /// For any non-trivial operations (e.g. window resizing, GL context creation, etc.), put them in /// [`WindowImpl::after_create`] instead. pub fn create_window( - title: &HSTRING, style: WindowStyle, nc_size: PhysicalSize, parent: HWND, + title: &HSTRING, style: WindowStyle, nc_size: PhysicalSize, parent: Option, _dpi_ctx: &DpiAwarenessContext, initializer: impl FnOnce(HWnd) -> W + 'static, ) -> Result { let instance = HInstance::get_from_dll(); @@ -69,7 +69,7 @@ pub fn create_window( 0, nc_size.width.try_into().unwrap_or(i32::MAX), nc_size.height.try_into().unwrap_or(i32::MAX), - parent, + parent.map(|p| p.as_raw()).unwrap_or(null_mut()), null_mut(), instance.as_raw(), Rc::into_raw(data).cast(), diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 8051a7d6..9f323095 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -23,7 +23,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ /// a handle from this type might still return an "invalid handle" error). /// /// The role of this type is to help safely encapsulating most of the unsafe Win32 HWND APIs. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct HWnd(HWND); impl HWnd {