From 34bfc325ebca6be70ba64c8fdf3bdd52e022afb1 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:50:52 +0200 Subject: [PATCH 1/5] wip --- Cargo.toml | 3 +- examples/cursors/Cargo.toml | 9 ++ examples/cursors/src/main.rs | 111 +++++++++++++++++++++ src/platform/macos/context.rs | 11 +- src/platform/macos/cursor.rs | 98 ++++++++++++------ src/platform/macos/view.rs | 19 ++-- src/platform/macos/window.rs | 2 - src/wrappers/appkit/view.rs | 2 + src/wrappers/appkit/view/implementation.rs | 20 +++- 9 files changed, 228 insertions(+), 47 deletions(-) create mode 100644 examples/cursors/Cargo.toml create mode 100644 examples/cursors/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 3cd07903..b8733e5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = ["s objc2-app-kit = { version = "0.3.2", default-features = false, features = [ "NSApplication", "NSCursor", + "NSImage", "NSDragging", "NSEvent", "NSGraphics", @@ -92,7 +93,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ ] } [workspace] -members = ["examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"] +members = ["examples/cursors", "examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"] [lints.clippy] missing-safety-doc = "allow" diff --git a/examples/cursors/Cargo.toml b/examples/cursors/Cargo.toml new file mode 100644 index 00000000..97c0caa0 --- /dev/null +++ b/examples/cursors/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cursors" +version = "0.1.0" +edition = "2024" + +[dependencies] +baseview = { path = "../..", features = ["opengl", "tracing"] } +femtovg = "0.26" +tracing-subscriber = { workspace = true } diff --git a/examples/cursors/src/main.rs b/examples/cursors/src/main.rs new file mode 100644 index 00000000..11055d66 --- /dev/null +++ b/examples/cursors/src/main.rs @@ -0,0 +1,111 @@ +use baseview::dpi::{LogicalSize, PhysicalPosition}; +use baseview::gl::{GlConfig, GlContext}; +use baseview::{ + Event, EventStatus, HandlerError, MouseCursor, MouseEvent, Window, WindowContext, + WindowHandler, WindowSettings, WindowSize, +}; +use femtovg::renderer::OpenGl; +use femtovg::{Canvas, Color}; +use std::cell::{Cell, RefCell}; + +struct CursorsExample { + window_context: WindowContext, + gl_context: GlContext, + canvas: RefCell>, + damaged: Cell, +} + +impl CursorsExample { + fn new(window_context: WindowContext) -> Result { + let Some(gl_context) = window_context.gl_context() else { unreachable!() }; + unsafe { gl_context.make_current()? }; + + let renderer = + unsafe { OpenGl::new_from_function_cstr(|s| gl_context.get_proc_address(s)) }?; + + let mut canvas = Canvas::new(renderer)?; + let size = window_context.size(); + + canvas.set_size(size.physical.width, size.physical.height, size.scale_factor as f32); + + unsafe { gl_context.make_not_current()? }; + Ok(Self { gl_context, window_context, canvas: canvas.into(), damaged: true.into() }) + } + + fn in_blue_area(&self, position: PhysicalPosition) -> bool { + let window_size = self.window_context.size().physical.cast::(); + let x = position.x / window_size.width; + let y = position.y / window_size.height; + + let is_outside = x < 0.1 || y < 0.1 || x > 0.9 || y > 0.9; + !is_outside + } +} + +impl WindowHandler for CursorsExample { + fn on_frame(&self) -> Result<(), HandlerError> { + if !self.damaged.get() { + return Ok(()); + } + + let context = &self.gl_context; + unsafe { context.make_current()? }; + + let mut canvas = self.canvas.borrow_mut(); + + let screen_height = canvas.height(); + let screen_width = canvas.width(); + + // Clear + canvas.clear_rect(0, 0, screen_width, screen_height, Color::rgb(0xAA, 0xAA, 0xAA)); + + // Make big blue rectangle + 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, + (screen_height as f32 * 0.8).floor() as u32, + Color::rgbf(0., 0.3, 0.9), + ); + + // Tell renderer to execute all drawing commands + canvas.flush(); + context.swap_buffers()?; + unsafe { context.make_not_current()? }; + self.damaged.set(false); + + Ok(()) + } + + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { + let size = new_size.physical; + self.canvas.borrow_mut().set_size(size.width, size.height, new_size.scale_factor as f32); + self.damaged.set(true); + + Ok(()) + } + + fn on_event(&self, event: Event) -> EventStatus { + if let Event::Mouse(MouseEvent::CursorMoved { position, .. }) = event { + if self.in_blue_area(position) { + self.window_context.set_mouse_cursor(MouseCursor::Working).unwrap(); + } else { + self.window_context.set_mouse_cursor(MouseCursor::Hand).unwrap(); + } + }; + + EventStatus::Captured + } +} + +fn main() -> Result<(), baseview::Error> { + tracing_subscriber::fmt::init(); + + let window_open_options = WindowSettings::new() + .with_title("Baseview cursors") + .with_size(LogicalSize::new(512, 512)) + .with_gl_config(GlConfig::default()); + + Window::create(window_open_options, CursorsExample::new)?.run_until_closed()?; + Ok(()) +} diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index f7579057..b20772d2 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -1,5 +1,4 @@ use crate::dpi::Size; -use crate::platform::macos::cursor::Cursor; use crate::platform::macos::view::BaseviewView; use crate::platform::Result; use crate::platform::{PlatformHandle, WindowSharedState}; @@ -75,12 +74,10 @@ impl WindowContext { pub fn set_mouse_cursor(&self, cursor: MouseCursor) -> Result<()> { let Some(view) = self.view.load() else { return Ok(()) }; - let native_cursor = Cursor::from(cursor); - if let Some(cursor) = native_cursor.load() { - view.addCursorRect_cursor(view.bounds(), &cursor); - } else { - NSCursor::hide() - } + let Some(view) = view.inner_ref() else { return Ok(()) }; + + view.inner.cursor_manager.set_cursor(cursor, view.view); + Ok(()) } diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs index 46587900..f896660c 100644 --- a/src/platform/macos/cursor.rs +++ b/src/platform/macos/cursor.rs @@ -1,12 +1,78 @@ +use crate::platform::macos::view::BaseviewView; +use crate::wrappers::appkit::View; +use crate::MouseCursor; use objc2::__framework_prelude::Retained; use objc2::runtime::{MessageReceiver, Sel}; -use objc2::{msg_send, sel, ClassType}; -use objc2_app_kit::NSCursor; +use objc2::{msg_send, sel, AnyThread, ClassType, Message}; +use objc2_app_kit::{NSCursor, NSImage}; +use objc2_foundation::{NSPoint, NSSize}; +use std::cell::{Cell, LazyCell, RefCell}; -use crate::MouseCursor; +pub struct CursorManager { + current: Cell, + current_cursor: RefCell>, + empty: LazyCell>, +} + +impl CursorManager { + pub fn new() -> Self { + Self { + current: MouseCursor::Default.into(), + current_cursor: NSCursor::arrowCursor().into(), + empty: LazyCell::new(Self::create_empty_cursor), + } + } + + fn create_empty_cursor() -> Retained { + let image = NSImage::initWithSize(NSImage::alloc(), NSSize::new(0.0, 0.0)); + NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, NSPoint::ZERO) + } + + pub fn set_cursor(&self, cursor: MouseCursor, view: &View) { + if self.current.get() == cursor { + return; + } + + self.current_cursor.replace(self.load(cursor.into())); + self.current.set(cursor); + + dbg!(cursor); + view.window().unwrap().enableCursorRects(); + + view.window().unwrap().invalidateCursorRectsForView(view); + eprintln!("invalidated cursor"); + } + + pub fn rebuild_cursor_rects(&self, view: &View) { + dbg!("rebuild cursor rects", view.bounds()); + view.addCursorRect_cursor(view.bounds(), &self.current_cursor.borrow()) + } + + fn load(&self, cursor: Cursor) -> Retained { + match cursor { + Cursor::Native(loader) => loader(), + Cursor::Undocumented(sel) => { + let class = NSCursor::class(); + + // NOTE: class.responds_to does not yield the same result (probably because NSCursor overrides respondsToSelector) + let responds_to: bool = unsafe { msg_send![class, respondsToSelector: sel] }; + + if !responds_to { + return NSCursor::arrowCursor(); + } + + let raw: *mut NSCursor = unsafe { class.send_message(sel, ()) }; + let cursor = unsafe { Retained::retain(raw) }; + + cursor.unwrap_or_else(NSCursor::arrowCursor) + } + Cursor::Hidden => self.empty.retain(), + } + } +} #[derive(Debug)] -pub enum Cursor { +enum Cursor { Native(fn() -> Retained), Undocumented(Sel), Hidden, @@ -63,27 +129,3 @@ impl From for Cursor { } } } - -impl Cursor { - pub fn load(&self) -> Option> { - match self { - Cursor::Native(loader) => Some(loader()), - Cursor::Undocumented(sel) => { - let class = NSCursor::class(); - - // NOTE: class.responds_to does not yield the same result (probably because NSCursor overrides respondsToSelector) - let responds_to: bool = unsafe { msg_send![class, respondsToSelector: *sel] }; - - if !responds_to { - return Some(NSCursor::arrowCursor()); - } - - let raw: *mut NSCursor = unsafe { class.send_message(*sel, ()) }; - let cursor = unsafe { Retained::retain(raw) }; - - Some(cursor.unwrap_or_else(NSCursor::arrowCursor)) - } - Cursor::Hidden => None, - } - } -} diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 909575db..48212376 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -4,6 +4,7 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; use crate::dpi::{LogicalPosition, LogicalSize, Size}; use crate::host::Host; +use crate::platform::macos::cursor::CursorManager; use crate::platform::*; use crate::tracing::warn; use crate::utils::SizingStrategy; @@ -20,7 +21,7 @@ use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::{msg_send, AllocAnyThread, ClassType, MainThreadMarker}; use objc2_app_kit::{ NSApplication, NSCursor, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, - NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, + NSResponder, NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell}; @@ -73,6 +74,7 @@ pub(crate) struct BaseviewView { pub(crate) lifetime_tied_to_app: Cell>>, host: Host, + pub(crate) cursor_manager: CursorManager, #[cfg(feature = "opengl")] pub(crate) gl_context: std::cell::OnceCell, @@ -103,6 +105,7 @@ impl BaseviewView { parenting: ViewParentingType::Uninitialized.into(), host: init.host, lifetime_tied_to_app: None.into(), + cursor_manager: CursorManager::new(), #[cfg(feature = "opengl")] gl_context: std::cell::OnceCell::new(), @@ -274,9 +277,6 @@ impl BaseviewView { impl Drop for BaseviewView { fn drop(&mut self) { self.state.closed.set(true); - if self.state.cursor_hidden.get() { - NSCursor::unhide(); - } } } @@ -416,9 +416,7 @@ impl ViewImpl for BaseviewView { } unsafe { - let superclass = msg_send![this.view, superclass]; - - let () = msg_send![super(this.view, superclass), viewWillMoveToWindow: new_window]; + let () = msg_send![super(this.view, NSView::class()), viewWillMoveToWindow: new_window]; } } @@ -446,6 +444,9 @@ impl ViewImpl for BaseviewView { modifiers: make_modifiers(event.modifierFlags()), }), ); + + // SAFETY: Our superclass is NSView + let _: () = unsafe { msg_send![super(this.view, NSView::class()), mouseMoved: event] }; } fn scroll_wheel(this: ViewRef, event: &NSEvent) { @@ -668,6 +669,10 @@ impl ViewImpl for BaseviewView { } } } + + fn reset_cursor_rects(this: ViewRef) { + this.cursor_manager.rebuild_cursor_rects(this.view) + } } /// Info: diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index cf128911..7596efa4 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -180,7 +180,6 @@ pub(crate) struct WindowSharedState { pub size: Cell>, pub scale_factor: Cell, pub sizing_strategy: SizingStrategy, - pub cursor_hidden: Cell, } impl WindowSharedState { @@ -190,7 +189,6 @@ impl WindowSharedState { size: size.into(), scale_factor: scale_factor.into(), sizing_strategy, - cursor_hidden: false.into(), } } } diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index ac8b3898..d972954b 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -189,4 +189,6 @@ pub trait ViewImpl: Sized { fn key_down(this: ViewRef, event: &NSEvent); fn key_up(this: ViewRef, event: &NSEvent); fn flags_changed(this: ViewRef, event: &NSEvent); + + fn reset_cursor_rects(this: ViewRef); } diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index dac23079..ca6cf76c 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -138,6 +138,11 @@ pub unsafe fn create_view_class() -> &'static AnyClass { class.add_method(sel!(keyDown:), key_down:: as extern "C-unwind" fn(_, _, _)); class.add_method(sel!(keyUp:), key_up:: as extern "C-unwind" fn(_, _, _)); class.add_method(sel!(flagsChanged:), flags_changed:: as extern "C-unwind" fn(_, _, _)); + + class.add_method( + sel!(resetCursorRects), + reset_cursor_rects:: as extern "C-unwind" fn(_, _), + ); } class.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR); @@ -264,14 +269,18 @@ extern "C-unwind" fn handle_notification( V::handle_notification(inner, notification) } -extern "C-unwind" fn mouse_entered(this: &View, _: Sel, _: &AnyObject) { +extern "C-unwind" fn mouse_entered(this: &View, _: Sel, event: &NSEvent) { let Some(inner) = this.inner_ref() else { return }; V::mouse_entered(inner); + // SAFETY: Our superclass is NSView + let _: () = unsafe { msg_send![super(this, NSView::class()), mouseEntered: event] }; } -extern "C-unwind" fn mouse_exited(this: &View, _: Sel, _: &AnyObject) { +extern "C-unwind" fn mouse_exited(this: &View, _: Sel, event: &NSEvent) { let Some(inner) = this.inner_ref() else { return }; V::mouse_exited(inner); + // SAFETY: Our superclass is NSView + let _: () = unsafe { msg_send![super(this, NSView::class()), mouseExited: event] }; } extern "C-unwind" fn key_down(this: &View, _: Sel, event: &NSEvent) { @@ -325,3 +334,10 @@ extern "C-unwind" fn window_did_resize( let Some(inner) = this.inner_ref() else { return }; V::window_did_resize(inner); } + +extern "C-unwind" fn reset_cursor_rects(this: &View, _sel: Sel) { + let Some(inner) = this.inner_ref() else { return }; + V::reset_cursor_rects(inner); + // SAFETY: Our superclass is NSView + let _: () = unsafe { msg_send![super(this, NSView::class()), resetCursorRects] }; +} From 432fb4c5d334b82d3421b8b737d088134aad04a0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 30 Aug 2026 12:11:27 +0200 Subject: [PATCH 2/5] wip --- src/platform/macos/context.rs | 2 +- src/platform/macos/cursor.rs | 26 +++++++++------- src/platform/macos/view.rs | 19 ++++++++---- src/platform/macos/window.rs | 1 + src/wrappers/appkit/view.rs | 3 +- src/wrappers/appkit/view/implementation.rs | 35 ++++++++++------------ 6 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index b20772d2..566065e6 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -76,7 +76,7 @@ impl WindowContext { let Some(view) = self.view.load() else { return Ok(()) }; let Some(view) = view.inner_ref() else { return Ok(()) }; - view.inner.cursor_manager.set_cursor(cursor, view.view); + view.inner.cursor_manager.set_cursor(cursor); Ok(()) } diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs index f896660c..597c4fcf 100644 --- a/src/platform/macos/cursor.rs +++ b/src/platform/macos/cursor.rs @@ -1,5 +1,3 @@ -use crate::platform::macos::view::BaseviewView; -use crate::wrappers::appkit::View; use crate::MouseCursor; use objc2::__framework_prelude::Retained; use objc2::runtime::{MessageReceiver, Sel}; @@ -9,6 +7,7 @@ use objc2_foundation::{NSPoint, NSSize}; use std::cell::{Cell, LazyCell, RefCell}; pub struct CursorManager { + is_inside: Cell, current: Cell, current_cursor: RefCell>, empty: LazyCell>, @@ -20,6 +19,7 @@ impl CursorManager { current: MouseCursor::Default.into(), current_cursor: NSCursor::arrowCursor().into(), empty: LazyCell::new(Self::create_empty_cursor), + is_inside: Cell::new(false), } } @@ -28,24 +28,28 @@ impl CursorManager { NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, NSPoint::ZERO) } - pub fn set_cursor(&self, cursor: MouseCursor, view: &View) { + pub fn set_is_inside(&self, is_inside: bool) { + self.is_inside.set(is_inside); + } + + pub fn set_cursor(&self, cursor: MouseCursor) { if self.current.get() == cursor { + self.update_to_current_cursor(); return; } self.current_cursor.replace(self.load(cursor.into())); self.current.set(cursor); - dbg!(cursor); - view.window().unwrap().enableCursorRects(); - - view.window().unwrap().invalidateCursorRectsForView(view); - eprintln!("invalidated cursor"); + if self.is_inside.get() { + self.update_to_current_cursor(); + } } - pub fn rebuild_cursor_rects(&self, view: &View) { - dbg!("rebuild cursor rects", view.bounds()); - view.addCursorRect_cursor(view.bounds(), &self.current_cursor.borrow()) + pub fn update_to_current_cursor(&self) { + //eprintln!("cursor set!"); + NSCursor::crosshairCursor().set(); + //self.current_cursor.borrow().set(); } fn load(&self, cursor: Cursor) -> Retained { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 48212376..2d34a568 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -422,6 +422,7 @@ impl ViewImpl for BaseviewView { fn update_tracking_areas(this: ViewRef) { let tracking_areas = this.view.trackingAreas(); + dbg!(tracking_areas.count()); if tracking_areas.count() > 0 { let tracking_area = tracking_areas.objectAtIndex(0); this.view.removeTrackingArea(&tracking_area); @@ -621,13 +622,22 @@ impl ViewImpl for BaseviewView { } fn mouse_entered(this: ViewRef) { + this.cursor_manager.set_is_inside(true); + // this.view.window().unwrap().disableCursorRects(); + // NSCursor::pointingHandCursor().push(); Self::trigger_event(this, Event::Mouse(MouseEvent::CursorEntered)); } fn mouse_exited(this: ViewRef) { + // this.cursor_manager.set_is_inside(false); + // NSCursor::pointingHandCursor().pop(); Self::trigger_event(this, Event::Mouse(MouseEvent::CursorLeft)); } + fn cursor_update(this: ViewRef) { + this.cursor_manager.update_to_current_cursor(); + } + fn key_down(this: ViewRef, event: &NSEvent) { if let Some(key_event) = this.keyboard_state.process_native_event(event) { let status = Self::trigger_event(this, Event::Keyboard(key_event)); @@ -669,10 +679,6 @@ impl ViewImpl for BaseviewView { } } } - - fn reset_cursor_rects(this: ViewRef) { - this.cursor_manager.rebuild_cursor_rects(this.view) - } } /// Info: @@ -683,7 +689,8 @@ fn new_tracking_area(this: &NSView) -> Retained { let options = NSTrackingAreaOptions::MouseEnteredAndExited | NSTrackingAreaOptions::MouseMoved | NSTrackingAreaOptions::CursorUpdate - | NSTrackingAreaOptions::ActiveInActiveApp + //| NSTrackingAreaOptions::ActiveInActiveApp + | NSTrackingAreaOptions::ActiveInKeyWindow | NSTrackingAreaOptions::InVisibleRect | NSTrackingAreaOptions::EnabledDuringMouseDrag; @@ -691,7 +698,7 @@ fn new_tracking_area(this: &NSView) -> Retained { unsafe { NSTrackingArea::initWithRect_options_owner_userInfo( NSTrackingArea::alloc(), - this.bounds(), + NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(0.0, 0.0)), options, Some(this), None, diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 7596efa4..c8981109 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -66,6 +66,7 @@ impl WindowHandle { init: WindowInitializer, mtm: MainThreadMarker, ) -> Result { let window = create_window_with_options(&init.settings, mtm); + window.setAcceptsMouseMovedEvents(true); let final_size = window.contentRectForFrameRect(window.frame()).size; let final_size = LogicalSize::new(final_size.width, final_size.height); diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index d972954b..d8dc9c31 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -185,10 +185,9 @@ pub trait ViewImpl: Sized { fn mouse_entered(this: ViewRef); fn mouse_exited(this: ViewRef); + fn cursor_update(this: ViewRef); fn key_down(this: ViewRef, event: &NSEvent); fn key_up(this: ViewRef, event: &NSEvent); fn flags_changed(this: ViewRef, event: &NSEvent); - - fn reset_cursor_rects(this: ViewRef); } diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index ca6cf76c..bf27bfac 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -66,8 +66,8 @@ pub unsafe fn create_view_class() -> &'static AnyClass { ); class.add_method(sel!(hitTest:), hit_test:: as extern "C-unwind" fn(_, _, _) -> _); class.add_method( - sel!(updateTrackingAreas:), - update_tracking_areas:: as extern "C-unwind" fn(_, _, _) -> _, + sel!(updateTrackingAreas), + update_tracking_areas:: as extern "C-unwind" fn(_, _) -> _, ); class.add_method(sel!(mouseMoved:), mouse_moved:: as extern "C-unwind" fn(_, _, _) -> _); @@ -90,8 +90,8 @@ pub unsafe fn create_view_class() -> &'static AnyClass { ); class.add_method( - sel!(viewDidChangeBackingProperties:), - view_did_change_backing_properties:: as extern "C-unwind" fn(_, _, _) -> _, + sel!(viewDidChangeBackingProperties), + view_did_change_backing_properties:: as extern "C-unwind" fn(_, _) -> _, ); class.add_method( @@ -139,10 +139,7 @@ pub unsafe fn create_view_class() -> &'static AnyClass { class.add_method(sel!(keyUp:), key_up:: as extern "C-unwind" fn(_, _, _)); class.add_method(sel!(flagsChanged:), flags_changed:: as extern "C-unwind" fn(_, _, _)); - class.add_method( - sel!(resetCursorRects), - reset_cursor_rects:: as extern "C-unwind" fn(_, _), - ); + class.add_method(sel!(cursorUpdate:), cursor_update:: as extern "C-unwind" fn(_, _, _)); } class.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR); @@ -192,9 +189,7 @@ extern "C-unwind" fn window_should_close( V::window_should_close(inner).into() } -extern "C-unwind" fn view_did_change_backing_properties( - this: &View, _: Sel, _: &AnyObject, -) { +extern "C-unwind" fn view_did_change_backing_properties(this: &View, _: Sel) { let Some(inner) = this.inner_ref() else { return }; V::view_did_change_backing_properties(inner, true); } @@ -212,7 +207,7 @@ extern "C-unwind" fn view_will_move_to_window( V::view_will_move_to_window(inner, new_window); } -extern "C-unwind" fn update_tracking_areas(this: &View, _self: Sel, _: &AnyObject) { +extern "C-unwind" fn update_tracking_areas(this: &View, _self: Sel) { let Some(inner) = this.inner_ref() else { return }; V::update_tracking_areas(inner); } @@ -270,17 +265,19 @@ extern "C-unwind" fn handle_notification( } extern "C-unwind" fn mouse_entered(this: &View, _: Sel, event: &NSEvent) { - let Some(inner) = this.inner_ref() else { return }; - V::mouse_entered(inner); // SAFETY: Our superclass is NSView let _: () = unsafe { msg_send![super(this, NSView::class()), mouseEntered: event] }; + + let Some(inner) = this.inner_ref() else { return }; + V::mouse_entered(inner); } extern "C-unwind" fn mouse_exited(this: &View, _: Sel, event: &NSEvent) { - let Some(inner) = this.inner_ref() else { return }; - V::mouse_exited(inner); // SAFETY: Our superclass is NSView let _: () = unsafe { msg_send![super(this, NSView::class()), mouseExited: event] }; + + let Some(inner) = this.inner_ref() else { return }; + V::mouse_exited(inner); } extern "C-unwind" fn key_down(this: &View, _: Sel, event: &NSEvent) { @@ -335,9 +332,7 @@ extern "C-unwind" fn window_did_resize( V::window_did_resize(inner); } -extern "C-unwind" fn reset_cursor_rects(this: &View, _sel: Sel) { +extern "C-unwind" fn cursor_update(this: &View, _sel: Sel, _: Option<&NSEvent>) { let Some(inner) = this.inner_ref() else { return }; - V::reset_cursor_rects(inner); - // SAFETY: Our superclass is NSView - let _: () = unsafe { msg_send![super(this, NSView::class()), resetCursorRects] }; + V::cursor_update(inner); } From 3c207db1ac8f5d0aa1bc297567de5770eafa98a9 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 30 Aug 2026 12:59:13 +0200 Subject: [PATCH 3/5] wip --- src/platform/macos/cursor.rs | 6 ++---- src/platform/macos/view.rs | 23 ++++++++++++---------- src/platform/macos/window.rs | 6 +++++- src/wrappers/appkit/view.rs | 2 +- src/wrappers/appkit/view/implementation.rs | 14 ++++++++++--- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs index 597c4fcf..232ad8d5 100644 --- a/src/platform/macos/cursor.rs +++ b/src/platform/macos/cursor.rs @@ -34,7 +34,6 @@ impl CursorManager { pub fn set_cursor(&self, cursor: MouseCursor) { if self.current.get() == cursor { - self.update_to_current_cursor(); return; } @@ -47,9 +46,8 @@ impl CursorManager { } pub fn update_to_current_cursor(&self) { - //eprintln!("cursor set!"); - NSCursor::crosshairCursor().set(); - //self.current_cursor.borrow().set(); + //NSCursor::crosshairCursor().set(); + self.current_cursor.borrow().set(); } fn load(&self, cursor: Cursor) -> Retained { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 2d34a568..16817019 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -20,10 +20,10 @@ use objc2::rc::Weak; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::{msg_send, AllocAnyThread, ClassType, MainThreadMarker}; use objc2_app_kit::{ - NSApplication, NSCursor, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, - NSResponder, NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, + NSApplication, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, NSResponder, + NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, }; -use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; +use objc2_foundation::{NSArray, NSNotification, NSPoint, NSPointInRect, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -422,7 +422,6 @@ impl ViewImpl for BaseviewView { fn update_tracking_areas(this: ViewRef) { let tracking_areas = this.view.trackingAreas(); - dbg!(tracking_areas.count()); if tracking_areas.count() > 0 { let tracking_area = tracking_areas.objectAtIndex(0); this.view.removeTrackingArea(&tracking_area); @@ -623,19 +622,23 @@ impl ViewImpl for BaseviewView { fn mouse_entered(this: ViewRef) { this.cursor_manager.set_is_inside(true); - // this.view.window().unwrap().disableCursorRects(); - // NSCursor::pointingHandCursor().push(); Self::trigger_event(this, Event::Mouse(MouseEvent::CursorEntered)); } fn mouse_exited(this: ViewRef) { - // this.cursor_manager.set_is_inside(false); - // NSCursor::pointingHandCursor().pop(); + this.cursor_manager.set_is_inside(false); Self::trigger_event(this, Event::Mouse(MouseEvent::CursorLeft)); } - fn cursor_update(this: ViewRef) { - this.cursor_manager.update_to_current_cursor(); + fn cursor_update(this: ViewRef, event: Option<&NSEvent>) -> bool { + let Some(event) = event else { return false }; + let point = this.view.convertPoint_fromView(event.locationInWindow(), None); + if NSPointInRect(point, this.view.frame()) { + this.cursor_manager.update_to_current_cursor(); + true + } else { + false + } } fn key_down(this: ViewRef, event: &NSEvent) { diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index c8981109..2bf16282 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,7 +1,10 @@ use crate::dpi::{LogicalSize, Size}; use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; -use objc2_app_kit::{NSApplication, NSPasteboard, NSPasteboardTypeString, NSView, NSWindow}; +use objc2_app_kit::{ + NSApplication, NSApplicationActivationPolicy, NSPasteboard, NSPasteboardTypeString, NSView, + NSWindow, +}; use objc2_foundation::{NSSize, NSString}; use std::cell::Cell; use std::rc::Rc; @@ -85,6 +88,7 @@ impl WindowHandle { BaseviewView::show(view); let app = NSApplication::sharedApplication(self.mtm); + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); view.lifetime_tied_to_app.set(Some(Weak::from_retained(&app))); app.run(); diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index d8dc9c31..85e3473c 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -185,7 +185,7 @@ pub trait ViewImpl: Sized { fn mouse_entered(this: ViewRef); fn mouse_exited(this: ViewRef); - fn cursor_update(this: ViewRef); + fn cursor_update(this: ViewRef, event: Option<&NSEvent>) -> bool; fn key_down(this: ViewRef, event: &NSEvent); fn key_up(this: ViewRef, event: &NSEvent); diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index bf27bfac..af16e10f 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -332,7 +332,15 @@ extern "C-unwind" fn window_did_resize( V::window_did_resize(inner); } -extern "C-unwind" fn cursor_update(this: &View, _sel: Sel, _: Option<&NSEvent>) { - let Some(inner) = this.inner_ref() else { return }; - V::cursor_update(inner); +extern "C-unwind" fn cursor_update( + this: &View, _sel: Sel, event: Option<&NSEvent>, +) { + if let Some(inner) = this.inner_ref() { + if V::cursor_update(inner, event) { + return; + }; + } + + // SAFETY: Our superclass is NSView + let _: () = unsafe { msg_send![super(this, NSView::class()), cursorUpdate: event] }; } From bba2efb7703000833f8ed98797bfe767b0d8aef9 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 30 Aug 2026 13:20:41 +0200 Subject: [PATCH 4/5] wip --- examples/cursors/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cursors/Cargo.toml b/examples/cursors/Cargo.toml index 97c0caa0..f358012f 100644 --- a/examples/cursors/Cargo.toml +++ b/examples/cursors/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cursors" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] baseview = { path = "../..", features = ["opengl", "tracing"] } From 94f25234b120cc42beb3444cdb5a969998164701 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 30 Aug 2026 13:35:38 +0200 Subject: [PATCH 5/5] clippy fix --- src/platform/macos/context.rs | 1 - src/platform/macos/view.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index 566065e6..a53170cb 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -8,7 +8,6 @@ use dispatch2::MainThreadBound; use objc2::rc::Weak; use objc2::runtime::NSObjectProtocol; use objc2::{MainThreadMarker, Message}; -use objc2_app_kit::NSCursor; use raw_window_handle::DisplayHandle; use std::rc::Rc; diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 16817019..53060b7d 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -20,8 +20,8 @@ use objc2::rc::Weak; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::{msg_send, AllocAnyThread, ClassType, MainThreadMarker}; use objc2_app_kit::{ - NSApplication, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, NSResponder, - NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, + NSApplication, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, NSTrackingArea, + NSTrackingAreaOptions, NSView, NSWindow, }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSPointInRect, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell};