From 1c9700684589aaae9eb50cd38b997e3359b7afa0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:29:13 +0200 Subject: [PATCH 1/7] wip --- examples/open_parented/src/main.rs | 60 ++++++++++++++------------ examples/open_window/src/main.rs | 66 +++++++++++++++-------------- examples/render_femtovg/src/main.rs | 58 ++++++++++++++----------- src/platform/win/window.rs | 20 ++++----- src/platform/x11/window.rs | 14 +++--- src/window.rs | 16 +++---- 6 files changed, 126 insertions(+), 108 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index f4cfba9a..8f7222c8 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -2,13 +2,14 @@ use baseview::{ Event, EventStatus, PhySize, Window, WindowEvent, WindowHandle, WindowHandler, WindowOpenOptions, }; +use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; struct ParentWindowHandler { _ctx: softbuffer::Context, - surface: softbuffer::Surface, - current_size: PhySize, - damaged: bool, + surface: RefCell, + current_size: Cell, + damaged: Cell, _child_window: Option, } @@ -28,36 +29,37 @@ impl ParentWindowHandler { // TODO: no way to query physical size initially? Self { _ctx: ctx, - surface, - current_size: PhySize::new(512, 512), - damaged: true, + surface: surface.into(), + current_size: PhySize::new(512, 512).into(), + damaged: true.into(), _child_window: Some(child_window), } } } impl WindowHandler for ParentWindowHandler { - fn on_frame(&mut self, _window: &mut Window) { - let mut buf = self.surface.buffer_mut().unwrap(); - if self.damaged { + fn on_frame(&self, _window: &mut Window) { + let mut surface = self.surface.borrow_mut(); + let mut buf = surface.buffer_mut().unwrap(); + if self.damaged.get() { buf.fill(0xFFAAAAAA); - self.damaged = false; + self.damaged.set(false); } buf.present().unwrap(); } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&self, _window: &mut Window, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { println!("Parent Resized: {:?}", info); let new_size = info.physical_size(); - self.current_size = new_size; + self.current_size.set(new_size); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) { - self.surface.resize(width, height).unwrap(); - self.damaged = true; + self.surface.borrow_mut().resize(width, height).unwrap(); + self.damaged.set(true); } } Event::Mouse(e) => println!("Parent Mouse event: {:?}", e), @@ -71,9 +73,9 @@ impl WindowHandler for ParentWindowHandler { struct ChildWindowHandler { _ctx: softbuffer::Context, - surface: softbuffer::Surface, - current_size: PhySize, - damaged: bool, + surface: RefCell, + current_size: Cell, + damaged: Cell, } impl ChildWindowHandler { @@ -83,32 +85,38 @@ impl ChildWindowHandler { surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); // TODO: no way to query physical size initially? - Self { _ctx: ctx, surface, current_size: PhySize::new(256, 256), damaged: true } + Self { + _ctx: ctx, + surface: surface.into(), + current_size: PhySize::new(256, 256).into(), + damaged: true.into(), + } } } impl WindowHandler for ChildWindowHandler { - fn on_frame(&mut self, _window: &mut Window) { - let mut buf = self.surface.buffer_mut().unwrap(); - if self.damaged { + fn on_frame(&self, _window: &mut Window) { + let mut surface = self.surface.borrow_mut(); + let mut buf = surface.buffer_mut().unwrap(); + if self.damaged.get() { buf.fill(0xFFAA0000); - self.damaged = false; + self.damaged.set(false); } buf.present().unwrap(); } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&self, _window: &mut Window, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { println!("Child Resized: {:?}", info); let new_size = info.physical_size(); - self.current_size = new_size; + self.current_size.set(new_size); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) { - self.surface.resize(width, height).unwrap(); - self.damaged = true; + self.surface.borrow_mut().resize(width, height).unwrap(); + self.damaged.set(true); } } Event::Mouse(e) => println!("Child Mouse event: {:?}", e), diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index 04da40d4..17124d56 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -1,3 +1,4 @@ +use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; use std::time::Duration; @@ -16,14 +17,14 @@ enum Message { } struct OpenWindowExample { - rx: Consumer, + rx: RefCell>, _ctx: softbuffer::Context, - surface: softbuffer::Surface, - current_size: WindowInfo, - mouse_pos: PhyPoint, - is_cursor_inside: bool, - damaged: bool, + surface: RefCell, + current_size: Cell, + mouse_pos: Cell, + is_cursor_inside: Cell, + damaged: Cell, } impl WindowHandler for OpenWindowExample { @@ -32,9 +33,10 @@ impl WindowHandler for OpenWindowExample { return; } - let mut pixels = self.surface.buffer_mut().unwrap(); - let size = self.current_size.physical_size(); - let scale_factor = self.current_size.scale(); + let mut surface = self.surface.borrow_mut(); + let mut pixels = surface.buffer_mut().unwrap(); + let size = self.current_size.get().physical_size(); + let scale_factor = self.current_size.get().scale(); let (width, height) = (size.width, size.height); for index in 0..(width * height) { @@ -75,10 +77,10 @@ impl WindowHandler for OpenWindowExample { if self.is_cursor_inside { let rect_size = (25.0 * scale_factor) as i32; - let rect_x_start = (self.mouse_pos.x - rect_size).clamp(0, width as i32) as u32; - let rect_x_end = (self.mouse_pos.x + rect_size).clamp(0, width as i32) as u32; - let rect_y_start = (self.mouse_pos.y - rect_size).clamp(0, height as i32) as u32; - let rect_y_end = (self.mouse_pos.y + rect_size).clamp(0, height as i32) as u32; + let rect_x_start = (self.mouse_pos.get().x - rect_size).clamp(0, width as i32) as u32; + let rect_x_end = (self.mouse_pos.get().x + rect_size).clamp(0, width as i32) as u32; + let rect_y_start = (self.mouse_pos.get().y - rect_size).clamp(0, height as i32) as u32; + let rect_y_end = (self.mouse_pos.get().y + rect_size).clamp(0, height as i32) as u32; for x in rect_x_start..rect_x_end { for y in rect_y_start..rect_y_end { @@ -89,41 +91,41 @@ impl WindowHandler for OpenWindowExample { } pixels.present().unwrap(); - self.damaged = false; + self.damaged.set(false); - while let Ok(message) = self.rx.pop() { + while let Ok(message) = self.rx.borrow_mut().pop() { println!("Message: {:?}", message); } } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&self, _window: &mut Window, event: Event) -> EventStatus { match &event { #[cfg(target_os = "macos")] Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"), Event::Mouse(MouseEvent::CursorMoved { position, .. }) => { - let phy_pos = position.to_physical(&self.current_size); - self.mouse_pos = phy_pos; - self.damaged = true; + let phy_pos = position.to_physical(&self.current_size.get()); + self.mouse_pos.set(phy_pos); + self.damaged.set(true); } Event::Mouse(MouseEvent::CursorEntered) => { - self.is_cursor_inside = true; - self.damaged = true; + self.is_cursor_inside.set(true); + self.damaged.set(true); } Event::Mouse(MouseEvent::CursorLeft) => { - self.is_cursor_inside = false; - self.damaged = true; + self.is_cursor_inside.set(false); + self.damaged.set(true); } Event::Window(WindowEvent::Resized(info)) => { println!("Resized: {:?}", info); - self.current_size = *info; + self.current_size.set(*info); let new_size = info.physical_size(); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) { - self.surface.resize(width, height).unwrap(); - self.damaged = true; + self.surface.borrow_mut().resize(width, height).unwrap(); + self.damaged.set(true); } } _ => {} @@ -155,12 +157,12 @@ fn main() { OpenWindowExample { _ctx: ctx, - surface, - rx, - current_size: WindowInfo::from_physical_size(PhySize::new(512, 512), 1.0), - mouse_pos: PhyPoint::new(0, 0), - is_cursor_inside: false, - damaged: true, + surface: surface.into(), + rx: rx.into(), + current_size: WindowInfo::from_physical_size(PhySize::new(512, 512).into(), 1.0).into(), + mouse_pos: PhyPoint::new(0, 0).into(), + is_cursor_inside: false.into(), + damaged: true.into(), } }); } diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index bd4af768..67a581ed 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -5,12 +5,13 @@ use baseview::{ }; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; +use std::cell::{Cell, RefCell}; struct FemtovgExample { - canvas: Canvas, - current_size: WindowInfo, - current_mouse_position: PhyPoint, - damaged: bool, + canvas: RefCell>, + current_size: Cell, + current_mouse_position: Cell, + damaged: Cell, } impl FemtovgExample { @@ -27,31 +28,34 @@ impl FemtovgExample { unsafe { context.make_not_current() }; Self { - canvas, - current_size: WindowInfo::from_logical_size(Size { width: 512.0, height: 512.0 }, 1.0), - current_mouse_position: PhyPoint { x: 256, y: 256 }, - damaged: true, + canvas: canvas.into(), + current_size: WindowInfo::from_logical_size(Size { width: 512.0, height: 512.0 }, 1.0) + .into(), + current_mouse_position: PhyPoint { x: 256, y: 256 }.into(), + damaged: true.into(), } } } impl WindowHandler for FemtovgExample { - fn on_frame(&mut self, window: &mut Window) { - if !self.damaged { + fn on_frame(&self, window: &mut Window) { + if !self.damaged.get() { return; } let context = window.gl_context().unwrap(); unsafe { context.make_current() }; - let screen_height = self.canvas.height(); - let screen_width = self.canvas.width(); + let mut canvas = self.canvas.borrow_mut(); + + let screen_height = canvas.height(); + let screen_width = canvas.width(); // Clear - self.canvas.clear_rect(0, 0, screen_width, screen_height, Color::rgb(0xAA, 0xAA, 0xAA)); + canvas.clear_rect(0, 0, screen_width, screen_height, Color::rgb(0xAA, 0xAA, 0xAA)); // Make big blue rectangle - self.canvas.clear_rect( + canvas.clear_rect( (screen_width as f32 * 0.1).floor() as u32, (screen_height as f32 * 0.1).floor() as u32, (screen_width as f32 * 0.8).floor() as u32, @@ -60,28 +64,32 @@ impl WindowHandler for FemtovgExample { ); // Make smol orange rectangle - self.canvas.clear_rect( - (self.current_mouse_position.x - 15).clamp(0, screen_width as i32 - 30) as u32, - (self.current_mouse_position.y - 15).clamp(0, screen_height as i32 - 30) as u32, + canvas.clear_rect( + (self.current_mouse_position.get().x - 15).clamp(0, screen_width as i32 - 30) as u32, + (self.current_mouse_position.get().y - 15).clamp(0, screen_height as i32 - 30) as u32, 30, 30, Color::rgbf(0.9, 0.3, 0.), ); // Tell renderer to execute all drawing commands - self.canvas.flush(); + canvas.flush(); context.swap_buffers(); unsafe { context.make_not_current() }; - self.damaged = false; + self.damaged.set(false); } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&self, _window: &mut Window, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(size)) => { let phy_size = size.physical_size(); - self.current_size = size; - self.canvas.set_size(phy_size.width, phy_size.height, size.scale() as f32); - self.damaged = true; + self.current_size.set(size); + self.canvas.borrow_mut().set_size( + phy_size.width, + phy_size.height, + size.scale() as f32, + ); + self.damaged.set(true); } Event::Mouse( MouseEvent::CursorMoved { position, .. } @@ -89,8 +97,8 @@ impl WindowHandler for FemtovgExample { | MouseEvent::DragMoved { position, .. } | MouseEvent::DragDropped { position, .. }, ) => { - self.current_mouse_position = position.to_physical(&self.current_size); - self.damaged = true; + self.current_mouse_position.set(position.to_physical(&self.current_size.get())); + self.damaged.set(true); } _ => {} }; diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index f7c45712..2a35b622 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -58,12 +58,12 @@ const WIN_FRAME_TIMER: NonZeroUsize = match NonZeroUsize::new(4242) { }; pub struct WindowHandle { - hwnd: Option, + hwnd: Cell>, is_open: Rc>, } impl WindowHandle { - pub fn close(&mut self) { + pub fn close(&self) { if let Some(hwnd) = self.hwnd.take() { unsafe { PostMessageW(hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); @@ -78,7 +78,7 @@ impl WindowHandle { unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { - if let Some(hwnd) = self.hwnd { + if let Some(hwnd) = self.hwnd.get() { let mut handle = Win32WindowHandle::empty(); handle.hwnd = hwnd; @@ -470,7 +470,7 @@ pub(crate) struct WindowState { mouse_was_outside_window: Cell, cursor_icon: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` - handler: RefCell>>, + handler: Option>, scale_policy: WindowScalePolicy, user32: ExtendedUser32, @@ -676,33 +676,33 @@ impl Window<'_> { window.show_and_activate(); - WindowHandle { hwnd: Some(hwnd), is_open: Rc::clone(&is_open) } + WindowHandle { hwnd: Some(hwnd).into(), is_open: Rc::clone(&is_open) } } - pub fn close(&mut self) { + pub fn close(&self) { unsafe { PostMessageW(self.state.hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); } } - pub fn has_focus(&mut self) -> bool { + pub fn has_focus(&self) -> bool { HWnd::get_focused_window() == self.state.hwnd } - pub fn focus(&mut self) { + pub fn focus(&self) { // To avoid reentrant event handler calls we'll defer the actual focus request until after // the event has been handled self.state.deferred_tasks.borrow_mut().push_back(WindowTask::Focus); } - pub fn resize(&mut self, size: Size) { + pub fn resize(&self, size: Size) { // To avoid reentrant event handler calls we'll defer the actual resizing until after the // event has been handled let task = WindowTask::Resize(size); self.state.deferred_tasks.borrow_mut().push_back(task); } - pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { self.state.cursor_icon.set(mouse_cursor); if let Ok(cursor) = SystemCursor::load(mouse_cursor) { cursor.set() diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index 820c4678..19f65e67 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -33,13 +33,13 @@ use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; pub struct WindowHandle { raw_window_handle: Option, - event_loop_handle: Option>, + event_loop_handle: Cell>>, close_requested: Arc, is_open: Arc, } impl WindowHandle { - pub fn close(&mut self) { + 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(); @@ -74,7 +74,7 @@ impl ParentHandle { let is_open = Arc::new(AtomicBool::new(true)); let handle = WindowHandle { raw_window_handle: None, - event_loop_handle: None, + event_loop_handle: None.into(), close_requested: Arc::clone(&close_requested), is_open: Arc::clone(&is_open), }; @@ -143,7 +143,7 @@ impl<'a> Window<'a> { let raw_window_handle = rx.recv().unwrap().unwrap(); window_handle.raw_window_handle = Some(raw_window_handle.0); - window_handle.event_loop_handle = Some(join_handle); + window_handle.event_loop_handle = Some(join_handle).into(); window_handle } @@ -302,7 +302,7 @@ impl<'a> Window<'a> { let mut window = crate::Window::new(Window { inner: &mut inner }); - let mut handler = build(&mut window); + let handler = build(&mut window); // Send an initial window resized event so the user is alerted of // the correct dpi scaling. @@ -333,7 +333,7 @@ impl<'a> Window<'a> { self.inner.mouse_cursor.set(mouse_cursor); } - pub fn close(&mut self) { + pub fn close(&self) { self.inner.close_requested.set(true); } @@ -350,7 +350,7 @@ impl<'a> Window<'a> { let _ = self.inner.xcb_connection.conn.flush(); } - pub fn resize(&mut self, size: Size) { + pub fn resize(&self, size: Size) { let scaling = self.inner.window_info.scale(); let new_window_info = WindowInfo::from_logical_size(size, scaling); diff --git a/src/window.rs b/src/window.rs index 2ac36603..747b4537 100644 --- a/src/window.rs +++ b/src/window.rs @@ -20,7 +20,7 @@ impl WindowHandle { } /// Close the window - pub fn close(&mut self) { + pub fn close(&self) { self.window_handle.close(); } @@ -38,8 +38,8 @@ unsafe impl HasRawWindowHandle for WindowHandle { } pub trait WindowHandler { - fn on_frame(&mut self, window: &mut Window); - fn on_event(&mut self, window: &mut Window, event: Event) -> EventStatus; + fn on_frame(&self, window: &mut Window); + fn on_event(&self, window: &mut Window, event: Event) -> EventStatus; } pub struct Window<'a> { @@ -81,25 +81,25 @@ impl<'a> Window<'a> { } /// Close the window - pub fn close(&mut self) { + pub fn close(&self) { self.window.close(); } /// Resize the window to the given size. The size is always in logical pixels. DPI scaling will /// automatically be accounted for. - pub fn resize(&mut self, size: Size) { + pub fn resize(&self, size: Size) { self.window.resize(size); } - pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) { + pub fn set_mouse_cursor(&self, cursor: MouseCursor) { self.window.set_mouse_cursor(cursor); } - pub fn has_focus(&mut self) -> bool { + pub fn has_focus(&self) -> bool { self.window.has_focus() } - pub fn focus(&mut self) { + pub fn focus(&self) { self.window.focus() } From 1d4924b39aee972fd205de4667fefcbae9b2e264 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 14 Jun 2026 03:36:25 +0200 Subject: [PATCH 2/7] Embrace reentrancy on macOS --- src/platform/macos/view.rs | 54 ++++++------------------------------ src/platform/macos/window.rs | 18 ++++++------ 2 files changed, 17 insertions(+), 55 deletions(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 53ec7fbf..af8ed7c4 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -18,7 +18,6 @@ use objc2_app_kit::{ }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; use std::rc::Rc; pub enum ViewParentingType { @@ -30,9 +29,6 @@ pub(crate) struct BaseviewView { pub(crate) state: Rc, window_handler: RefCell>>, - /// Events that will be triggered at the end of `window_handler`'s borrow. - deferred_events: RefCell>, - frame_timer: Cell>, notification_center_observer: Cell>, @@ -57,7 +53,6 @@ impl BaseviewView { let inner = BaseviewView { state: state.clone(), - deferred_events: RefCell::default(), keyboard_state: KeyboardState::new(), frame_timer: None.into(), window_handler: None.into(), @@ -114,7 +109,7 @@ impl BaseviewView { view.notification_center_observer.set(Some(observer)); // Send an initial Resized event so users get the correct scale factor and physical size. - Self::trigger_deferrable_event( + Self::trigger_event( view, Event::Window(WindowEvent::Resized(Self::fetch_view_size(view.view))), ); @@ -166,51 +161,21 @@ impl BaseviewView { } /// Trigger the event immediately and return the event status. - /// Will panic if `window_handler` is already borrowed (see `trigger_deferrable_event`). fn trigger_event(this: ViewRef, event: Event) -> EventStatus { - let mut handler = this.window_handler.borrow_mut(); - let Some(handler) = handler.as_mut() else { + let handler = this.window_handler.borrow(); + let Some(handler) = handler.as_ref() else { return EventStatus::Ignored; }; let status = handler.on_event(&mut this.into(), event); - Self::send_deferred_events(this, handler.as_mut()); status } - /// Trigger the event immediately if `window_handler` can be borrowed mutably, - /// otherwise add the event to a queue that will be cleared once `window_handler`'s mutable borrow ends. - /// As this method might result in the event triggering asynchronously, it can't reliably return the event status. - fn trigger_deferrable_event(this: ViewRef, event: Event) { - let Ok(mut handler) = this.window_handler.try_borrow_mut() else { - this.deferred_events.borrow_mut().push_back(event); - return; - }; - - let Some(handler) = handler.as_mut() else { return }; - - handler.on_event(&mut this.into(), event); - Self::send_deferred_events(this, handler.as_mut()); - } - fn trigger_frame(this: ViewRef) { - let mut handler = this.window_handler.borrow_mut(); - let Some(handler) = handler.as_mut() else { return }; + let handler = this.window_handler.borrow(); + let Some(handler) = handler.as_ref() else { return }; handler.on_frame(&mut this.into()); - Self::send_deferred_events(this, handler.as_mut()); - } - - fn send_deferred_events(this: ViewRef, window_handler: &mut dyn WindowHandler) { - let mut window = this.into(); - loop { - let next_event = { this.deferred_events.borrow_mut().pop_front() }; - if let Some(event) = next_event { - window_handler.on_event(&mut window, event); - } else { - break; - } - } } fn fetch_view_size(view: &NSView) -> WindowInfo { @@ -240,14 +205,14 @@ impl ViewImpl for BaseviewView { }; if window.isKeyWindow() { - Self::trigger_deferrable_event(this, Event::Window(WindowEvent::Focused)); + Self::trigger_event(this, Event::Window(WindowEvent::Focused)); } true } fn resign_first_responder(this: ViewRef) -> bool { - Self::trigger_deferrable_event(this, Event::Window(WindowEvent::Unfocused)); + Self::trigger_event(this, Event::Window(WindowEvent::Unfocused)); true } @@ -266,10 +231,7 @@ impl ViewImpl for BaseviewView { // other platform implementations if new_window_info.physical_size() != window_info.physical_size() { this.state.window_info.set(new_window_info); - Self::trigger_deferrable_event( - this, - Event::Window(WindowEvent::Resized(new_window_info)), - ); + Self::trigger_event(this, Event::Window(WindowEvent::Resized(new_window_info))); } } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 8e07bf55..64405957 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,4 +1,4 @@ -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use objc2::rc::{autoreleasepool, Weak}; @@ -22,12 +22,12 @@ use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::wrappers::appkit::{create_window, extract_raw_window_handle, View, ViewRef}; pub struct WindowHandle { - view: Option>>, + view: RefCell>>>, state: Rc, } impl WindowHandle { - pub fn close(&mut self) { + pub fn close(&self) { let Some(view) = self.view.take().and_then(|w| w.load()) else { return; }; @@ -42,7 +42,7 @@ impl WindowHandle { unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { - let Some(view) = self.view.as_ref().and_then(|w| w.load()) else { + let Some(view) = self.view.borrow().as_ref().and_then(|w| w.load()) else { return AppKitWindowHandle::empty().into(); }; @@ -82,7 +82,7 @@ impl<'a> Window<'a> { let (ns_view, state) = BaseviewView::new(options, build, parenting); - WindowHandle { view: Some(Weak::from_retained(&ns_view)), state } + WindowHandle { view: Some(Weak::from_retained(&ns_view)).into(), state } }) } @@ -120,11 +120,11 @@ impl<'a> Window<'a> { }) } - pub fn close(&mut self) { + pub fn close(&self) { BaseviewView::close(self.view.inner_ref()); } - pub fn has_focus(&mut self) -> bool { + pub fn has_focus(&self) -> bool { let Some(window) = self.view.window() else { return false; }; @@ -140,13 +140,13 @@ impl<'a> Window<'a> { self.view.isEqual(Some(&*first_responder)) } - pub fn focus(&mut self) { + pub fn focus(&self) { if let Some(window) = self.view.window() { window.makeFirstResponder(Some(self.view)); } } - pub fn resize(&mut self, size: Size) { + pub fn resize(&self, size: Size) { if self.inner.state.closed.get() { return; } From 3723a91370b3a673882b00b0ee4ef60d02f6eb8a Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 14 Jun 2026 03:39:00 +0200 Subject: [PATCH 3/7] fix --- src/platform/macos/view.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index af8ed7c4..ef970166 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -167,8 +167,7 @@ impl BaseviewView { return EventStatus::Ignored; }; - let status = handler.on_event(&mut this.into(), event); - status + handler.on_event(&mut this.into(), event) } fn trigger_frame(this: ViewRef) { From b066b0186fb8e6ae105a5d50697f35204d1f1d6b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:37:32 +0200 Subject: [PATCH 4/7] Embrace reentrancy on Win32 --- src/platform/win/window.rs | 105 +++++++++---------------------------- 1 file changed, 24 insertions(+), 81 deletions(-) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 2a35b622..62eef1e0 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -13,8 +13,7 @@ use windows_sys::Win32::{ }, }; -use std::cell::{Cell, Ref, RefCell}; -use std::collections::VecDeque; +use std::cell::{Cell, OnceCell, Ref, RefCell}; use std::num::NonZeroUsize; use std::ptr::null_mut; use std::rc::Rc; @@ -173,7 +172,7 @@ impl WindowImpl for BaseviewWindow { self.handler_builder.take().unwrap()(&mut window) }; - *window_state.handler.borrow_mut() = Some(handler); + let Ok(()) = window_state.handler.set(handler) else { unreachable!() }; if dpi_changed { // Send an initial Resized event so users get the correct scale factor and physical size. @@ -186,24 +185,7 @@ impl WindowImpl for BaseviewWindow { unsafe fn handle_message( &self, window: HWnd, msg: u32, wparam: WPARAM, lparam: LPARAM, ) -> Option { - let result = unsafe { wnd_proc_inner(window, msg, wparam, lparam, &self.window_state) }; - - // If any of the above event handlers caused tasks to be pushed to the deferred tasks list, - // then we'll try to handle them now - loop { - // NOTE: This is written like this instead of using a `while let` loop to avoid exending - // the borrow of `window_state.deferred_tasks` into the call of - // `window_state.handle_deferred_task()` since that may also generate additional - // messages. - let task = match self.window_state.deferred_tasks.borrow_mut().pop_front() { - Some(task) => task, - None => break, - }; - - self.window_state.handle_deferred_task(task, window); - } - - result + unsafe { wnd_proc_inner(window, msg, wparam, lparam, &self.window_state) } } fn before_destroy(&self, window: HWnd) { @@ -453,15 +435,9 @@ unsafe fn wnd_proc_inner( } } -/// All data associated with the window. This uses internal mutability so the outer struct doesn't -/// need to be mutably borrowed. Mutably borrowing the entire `WindowState` can be problematic -/// because of the Windows message loops' reentrant nature. Care still needs to be taken to prevent -/// `handler` from indirectly triggering other events that would also need to be handled using -/// `handler`. +/// All data associated with the window. pub(crate) struct WindowState { - /// The HWND belonging to this window. The window's actual state is stored in the `WindowState` - /// struct associated with this HWND through `unsafe { GetWindowLongPtrW(self.hwnd, - /// GWLP_USERDATA) } as *const WindowState`. + /// The HWND belonging to this window. pub hwnd: HWND, current_size: Cell, current_dpi: Cell, // None if in non-system scale policy @@ -470,20 +446,13 @@ pub(crate) struct WindowState { mouse_was_outside_window: Cell, cursor_icon: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` - handler: Option>, + handler: OnceCell>, scale_policy: WindowScalePolicy, user32: ExtendedUser32, - /// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably - /// borrowing the fields from `WindowState` more than once. For instance, when the window - /// handler requests a resize in response to a keyboard event, the window state will already be - /// borrowed in `wnd_proc`. So the `resize()` function below cannot also mutably borrow that - /// window state at the same time. - pub deferred_tasks: RefCell>, - #[cfg(feature = "opengl")] - pub gl_context: core::cell::OnceCell, + pub gl_context: OnceCell, } impl WindowState { @@ -498,29 +467,24 @@ impl WindowState { mouse_button_counter: Cell::new(0), mouse_was_outside_window: true.into(), cursor_icon: Cell::new(MouseCursor::Default), - handler: RefCell::new(None), + handler: OnceCell::new(), scale_policy, user32, - deferred_tasks: RefCell::new(VecDeque::with_capacity(4)), - #[cfg(feature = "opengl")] - gl_context: core::cell::OnceCell::new(), + gl_context: OnceCell::new(), } } pub(crate) fn handle_on_frame(&self) { - let mut handler = self.handler.borrow_mut(); - let Some(handler) = handler.as_mut() else { return }; + let Some(handler) = self.handler.get() else { return }; let mut window = crate::window::Window::new(Window { state: self }); handler.on_frame(&mut window) } pub(crate) fn handle_event(&self, event: Event) -> EventStatus { - let mut handler = self.handler.borrow_mut(); - - let Some(handler) = handler.as_mut() else { + let Some(handler) = self.handler.get() else { return EventStatus::Ignored; }; @@ -546,33 +510,6 @@ impl WindowState { fn send_resized(&self) { self.handle_event(Event::Window(WindowEvent::Resized(self.window_info()))); } - - /// Handle a deferred task as described in [`Self::deferred_tasks`]. - pub(self) fn handle_deferred_task(&self, task: WindowTask, window: HWnd) { - match task { - WindowTask::Resize(size) => { - // `self.window_info` will be modified in response to the `WM_SIZE` event that - // follows the `SetWindowPos()` call - let dpi = self.current_dpi.get(); - let window_info = WindowInfo::from_logical_size(size, dpi.scale_factor()); - let new_size = window_info.physical_size(); - - window.resize_and_activate(new_size, dpi, &self.user32).unwrap(); - } - WindowTask::Focus => window.set_focus().unwrap(), - } - } -} - -/// Tasks that must be deferred until the end of [`wnd_proc()`] to avoid reentrant `WindowState` -/// borrows. See the docstring on [`WindowState::deferred_tasks`] for more information. -#[derive(Debug, Clone)] -pub(crate) enum WindowTask { - /// Resize the window to the given size. The size is in logical pixels. DPI scaling is applied - /// automatically. - Resize(Size), - /// Request keyboard focus for the window. - Focus, } pub struct Window<'a> { @@ -690,16 +627,22 @@ impl Window<'_> { } pub fn focus(&self) { - // To avoid reentrant event handler calls we'll defer the actual focus request until after - // the event has been handled - self.state.deferred_tasks.borrow_mut().push_back(WindowTask::Focus); + self.hwnd().set_focus().unwrap() + } + + fn hwnd(&self) -> HWnd<'_> { + // SAFETY: this handle should be safe to use + unsafe { HWnd::from_raw(self.state.hwnd) } } pub fn resize(&self, size: Size) { - // To avoid reentrant event handler calls we'll defer the actual resizing until after the - // event has been handled - let task = WindowTask::Resize(size); - self.state.deferred_tasks.borrow_mut().push_back(task); + // `self.window_info` will be modified in response to the `WM_SIZE` event that + // follows the `SetWindowPos()` call + let scaling = self.state.current_scale_factor.get(); + let window_info = WindowInfo::from_logical_size(size, scaling); + let new_size = window_info.physical_size(); + + self.hwnd().resize_and_activate(new_size, self.state.dw_style).unwrap(); } pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { From 770eca81e62375c98bc8eb67c79ac9298660a463 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 15 Jun 2026 03:59:12 +0200 Subject: [PATCH 5/7] Replace handler's RefCell with OnceCell --- examples/open_window/src/main.rs | 6 +++--- src/platform/macos/view.rs | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index 17124d56..f8c6fd36 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -28,8 +28,8 @@ struct OpenWindowExample { } impl WindowHandler for OpenWindowExample { - fn on_frame(&mut self, _window: &mut Window) { - if !self.damaged { + fn on_frame(&self, _window: &mut Window) { + if !self.damaged.get() { return; } @@ -74,7 +74,7 @@ impl WindowHandler for OpenWindowExample { pixels[index as usize] = if (y % 10) < 5 { 0xFFFF00FF } else { 0xFF000000 }; } - if self.is_cursor_inside { + if self.is_cursor_inside.get() { let rect_size = (25.0 * scale_factor) as i32; let rect_x_start = (self.mouse_pos.get().x - rect_size).clamp(0, width as i32) as u32; diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index ef970166..d2180c6c 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -17,7 +17,7 @@ use objc2_app_kit::{ NSTrackingAreaOptions, NSView, NSWindow, }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, OnceCell}; use std::rc::Rc; pub enum ViewParentingType { @@ -27,7 +27,7 @@ pub enum ViewParentingType { pub(crate) struct BaseviewView { pub(crate) state: Rc, - window_handler: RefCell>>, + window_handler: OnceCell>, frame_timer: Cell>, notification_center_observer: Cell>, @@ -37,7 +37,7 @@ pub(crate) struct BaseviewView { parenting: ViewParentingType, #[cfg(feature = "opengl")] - pub(crate) gl_context: std::cell::OnceCell, + pub(crate) gl_context: OnceCell, } impl BaseviewView { @@ -55,12 +55,12 @@ impl BaseviewView { keyboard_state: KeyboardState::new(), frame_timer: None.into(), - window_handler: None.into(), + window_handler: OnceCell::new(), notification_center_observer: None.into(), parenting, #[cfg(feature = "opengl")] - gl_context: std::cell::OnceCell::new(), + gl_context: OnceCell::new(), }; let view = View::new(view_rect, inner, |view| { @@ -85,7 +85,9 @@ impl BaseviewView { } // Initialize handler - view.window_handler.replace(Some(Box::new(builder(&mut view.into())))); + let Ok(()) = view.window_handler.set(Box::new(builder(&mut view.into()))) else { + unreachable!() + }; // Set up anything that might trigger events to the handler @@ -162,8 +164,7 @@ impl BaseviewView { /// Trigger the event immediately and return the event status. fn trigger_event(this: ViewRef, event: Event) -> EventStatus { - let handler = this.window_handler.borrow(); - let Some(handler) = handler.as_ref() else { + let Some(handler) = this.window_handler.get() else { return EventStatus::Ignored; }; @@ -171,8 +172,7 @@ impl BaseviewView { } fn trigger_frame(this: ViewRef) { - let handler = this.window_handler.borrow(); - let Some(handler) = handler.as_ref() else { return }; + let Some(handler) = this.window_handler.get() else { return }; handler.on_frame(&mut this.into()); } From f6d8a443549628208a911ce62b66c06c5bf246ca Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:13:02 +0200 Subject: [PATCH 6/7] clippy fix --- examples/open_window/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index f8c6fd36..84d9df63 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -159,7 +159,7 @@ fn main() { _ctx: ctx, surface: surface.into(), rx: rx.into(), - current_size: WindowInfo::from_physical_size(PhySize::new(512, 512).into(), 1.0).into(), + current_size: WindowInfo::from_physical_size(PhySize::new(512, 512), 1.0).into(), mouse_pos: PhyPoint::new(0, 0).into(), is_cursor_inside: false.into(), damaged: true.into(), From a8605afdd89b4dfd7638ce7dab7bfc03d6cde739 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:27:41 +0200 Subject: [PATCH 7/7] Win32 fix --- src/platform/win/window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 62eef1e0..3209ed08 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -638,11 +638,11 @@ impl Window<'_> { pub fn resize(&self, size: Size) { // `self.window_info` will be modified in response to the `WM_SIZE` event that // follows the `SetWindowPos()` call - let scaling = self.state.current_scale_factor.get(); - let window_info = WindowInfo::from_logical_size(size, scaling); + let dpi = self.state.current_dpi.get(); + let window_info = WindowInfo::from_logical_size(size, dpi.scale_factor()); let new_size = window_info.physical_size(); - self.hwnd().resize_and_activate(new_size, self.state.dw_style).unwrap(); + self.hwnd().resize_and_activate(new_size, dpi, &self.state.user32).unwrap(); } pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) {