diff --git a/src/event.rs b/src/event.rs index 64431a09..ab2eab23 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,8 +38,12 @@ pub enum ScrollDelta { pub enum MouseEvent { /// The mouse cursor was moved CursorMoved { - /// The logical coordinates of the mouse position + /// The logical coordinates of the mouse position (window-relative) position: Point, + /// The logical coordinates of the mouse position in screen space (absolute) + /// This remains constant even when the window resizes, making it suitable + /// for drag operations that resize the window. + screen_position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, }, @@ -79,8 +83,10 @@ pub enum MouseEvent { CursorLeft, DragEntered { - /// The logical coordinates of the mouse position + /// The logical coordinates of the mouse position (window-relative) position: Point, + /// The logical coordinates of the mouse position in screen space (absolute) + screen_position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, /// Data being dragged @@ -88,8 +94,10 @@ pub enum MouseEvent { }, DragMoved { - /// The logical coordinates of the mouse position + /// The logical coordinates of the mouse position (window-relative) position: Point, + /// The logical coordinates of the mouse position in screen space (absolute) + screen_position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, /// Data being dragged @@ -99,8 +107,10 @@ pub enum MouseEvent { DragLeft, DragDropped { - /// The logical coordinates of the mouse position + /// The logical coordinates of the mouse position (window-relative) position: Point, + /// The logical coordinates of the mouse position in screen space (absolute) + screen_position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, /// Data being dragged diff --git a/src/macos/cursor.rs b/src/macos/cursor.rs new file mode 100644 index 00000000..592e677c --- /dev/null +++ b/src/macos/cursor.rs @@ -0,0 +1,53 @@ +use crate::MouseCursor; +use cocoa::base::id; +use objc::{class, msg_send, sel, sel_impl}; + +pub fn mouse_cursor_to_nscursor(cursor: MouseCursor) -> id { + unsafe { + let nscursor_class = class!(NSCursor); + match cursor { + MouseCursor::Default => msg_send![nscursor_class, arrowCursor], + MouseCursor::Hand => msg_send![nscursor_class, pointingHandCursor], + MouseCursor::HandGrabbing => msg_send![nscursor_class, closedHandCursor], + MouseCursor::Help => msg_send![nscursor_class, arrowCursor], // No help cursor + MouseCursor::Hidden => { + // Return a null cursor for hidden - will be handled specially + std::ptr::null_mut() + } + MouseCursor::Text => msg_send![nscursor_class, IBeamCursor], + MouseCursor::VerticalText => msg_send![nscursor_class, IBeamCursor], + MouseCursor::Working => msg_send![nscursor_class, arrowCursor], + MouseCursor::PtrWorking => msg_send![nscursor_class, arrowCursor], + MouseCursor::NotAllowed => msg_send![nscursor_class, operationNotAllowedCursor], + MouseCursor::PtrNotAllowed => msg_send![nscursor_class, operationNotAllowedCursor], + MouseCursor::ZoomIn => msg_send![nscursor_class, arrowCursor], + MouseCursor::ZoomOut => msg_send![nscursor_class, arrowCursor], + MouseCursor::Alias => msg_send![nscursor_class, dragLinkCursor], + MouseCursor::Copy => msg_send![nscursor_class, dragCopyCursor], + MouseCursor::Move => msg_send![nscursor_class, arrowCursor], + MouseCursor::AllScroll => msg_send![nscursor_class, arrowCursor], + MouseCursor::Cell => msg_send![nscursor_class, crosshairCursor], + MouseCursor::Crosshair => msg_send![nscursor_class, crosshairCursor], + MouseCursor::EResize => msg_send![nscursor_class, resizeRightCursor], + MouseCursor::NResize => msg_send![nscursor_class, resizeUpCursor], + MouseCursor::NeResize => msg_send![nscursor_class, arrowCursor], // No built-in + MouseCursor::NwResize => msg_send![nscursor_class, arrowCursor], // No built-in + MouseCursor::SResize => msg_send![nscursor_class, resizeDownCursor], + MouseCursor::SeResize => msg_send![nscursor_class, arrowCursor], // No built-in + MouseCursor::SwResize => msg_send![nscursor_class, arrowCursor], // No built-in + MouseCursor::WResize => msg_send![nscursor_class, resizeLeftCursor], + MouseCursor::EwResize => msg_send![nscursor_class, resizeLeftRightCursor], + MouseCursor::NsResize => msg_send![nscursor_class, resizeUpDownCursor], + MouseCursor::NwseResize => { + // Use private API for diagonal resize cursor + msg_send![nscursor_class, _windowResizeNorthWestSouthEastCursor] + } + MouseCursor::NeswResize => { + // Use private API for diagonal resize cursor + msg_send![nscursor_class, _windowResizeNorthEastSouthWestCursor] + } + MouseCursor::ColResize => msg_send![nscursor_class, resizeLeftRightCursor], + MouseCursor::RowResize => msg_send![nscursor_class, resizeUpDownCursor], + } + } +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index d5e7f591..409a7b0a 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -2,6 +2,7 @@ // Eventually we should migrate to the objc2 crate and remove this. #![allow(unexpected_cfgs)] +mod cursor; mod keyboard; mod view; mod window; diff --git a/src/macos/view.rs b/src/macos/view.rs index 063ba24c..54dfb0be 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -393,17 +393,38 @@ extern "C" fn update_tracking_areas(this: &Object, _self: Sel, _: id) { extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) { let state = unsafe { WindowState::from_view(this) }; - let point: NSPoint = unsafe { + // Window-relative position (existing behavior) + let window_point: NSPoint = unsafe { let point = NSEvent::locationInWindow(event); - msg_send![this, convertPoint:point fromView:nil] }; + + // Screen-absolute position (new!) + // NSEvent::mouseLocation returns screen coordinates with Y=0 at BOTTOM + // We need to flip Y-axis to match Windows/X11 convention (Y=0 at TOP) + let screen_point: NSPoint = unsafe { + NSEvent::mouseLocation(event) + }; + + // Get the screen height to flip Y coordinate + let screen_height = unsafe { + let screen: id = msg_send![class!(NSScreen), mainScreen]; + let frame: NSRect = msg_send![screen, frame]; + frame.size.height + }; + let modifiers = unsafe { NSEvent::modifierFlags(event) }; - let position = Point { x: point.x, y: point.y }; + let position = Point { x: window_point.x, y: window_point.y }; + // Flip Y-axis: convert from bottom-origin to top-origin + let screen_position = Point { + x: screen_point.x, + y: screen_height - screen_point.y + }; state.trigger_event(Event::Mouse(MouseEvent::CursorMoved { position, + screen_position, modifiers: make_modifiers(modifiers), })); } @@ -430,9 +451,29 @@ extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) { })); } -fn get_drag_position(sender: id) -> Point { - let point: NSPoint = unsafe { msg_send![sender, draggingLocation] }; - Point::new(point.x, point.y) +fn get_drag_position(sender: id) -> (Point, Point) { + // Window-relative position + let window_point: NSPoint = unsafe { msg_send![sender, draggingLocation] }; + + // For drag events, we need to get screen position from the global mouse location + // since NSDraggingInfo doesn't provide it directly + // NSEvent::mouseLocation returns coordinates with Y=0 at BOTTOM + let screen_point: NSPoint = unsafe { + let ns_event_class: id = msg_send![class!(NSEvent), class]; + msg_send![ns_event_class, mouseLocation] + }; + + // Get screen height to flip Y coordinate (convert from bottom-origin to top-origin) + let screen_height = unsafe { + let screen: id = msg_send![class!(NSScreen), mainScreen]; + let frame: NSRect = msg_send![screen, frame]; + frame.size.height + }; + + ( + Point::new(window_point.x, window_point.y), + Point::new(screen_point.x, screen_height - screen_point.y) + ) } fn get_drop_data(sender: id) -> DropData { @@ -473,9 +514,11 @@ extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteg let state = unsafe { WindowState::from_view(this) }; let modifiers = state.keyboard_state().last_mods(); let drop_data = get_drop_data(sender); + let (position, screen_position) = get_drag_position(sender); let event = MouseEvent::DragEntered { - position: get_drag_position(sender), + position, + screen_position, modifiers: make_modifiers(modifiers), data: drop_data, }; @@ -487,9 +530,11 @@ extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteg let state = unsafe { WindowState::from_view(this) }; let modifiers = state.keyboard_state().last_mods(); let drop_data = get_drop_data(sender); + let (position, screen_position) = get_drag_position(sender); let event = MouseEvent::DragMoved { - position: get_drag_position(sender), + position, + screen_position, modifiers: make_modifiers(modifiers), data: drop_data, }; @@ -508,9 +553,11 @@ extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: id) -> BO let state = unsafe { WindowState::from_view(this) }; let modifiers = state.keyboard_state().last_mods(); let drop_data = get_drop_data(sender); + let (position, screen_position) = get_drag_position(sender); let event = MouseEvent::DragDropped { - position: get_drag_position(sender), + position, + screen_position, modifiers: make_modifiers(modifiers), data: drop_data, }; diff --git a/src/macos/window.rs b/src/macos/window.rs index 57bca108..eb182c1f 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -334,8 +334,18 @@ impl<'a> Window<'a> { } } - pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) { - todo!() + pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { + let ns_cursor = crate::macos::cursor::mouse_cursor_to_nscursor(mouse_cursor); + + unsafe { + if ns_cursor.is_null() { + // Hide cursor + let _: () = msg_send![class!(NSCursor), hide]; + } else { + // Set the cursor + let _: () = msg_send![ns_cursor, set]; + } + } } #[cfg(feature = "opengl")] diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs index 1b7835f1..c48ee2b8 100644 --- a/src/win/drop_target.rs +++ b/src/win/drop_target.rs @@ -70,6 +70,7 @@ pub(super) struct DropTarget { // These are cached since DragOver and DragLeave callbacks don't provide them, // and handling drag move events gets awkward on the client end otherwise drag_position: Point, + drag_screen_position: Point, drop_data: DropData, } @@ -81,6 +82,7 @@ impl DropTarget { window_state, drag_position: Point::new(0.0, 0.0), + drag_screen_position: Point::new(0.0, 0.0), drop_data: DropData::None, } } @@ -114,10 +116,16 @@ impl DropTarget { let Some(window_state) = self.window_state.upgrade() else { return; }; - let mut pt = POINT { x: pt.x, y: pt.y }; - unsafe { ScreenToClient(window_state.hwnd, &mut pt as *mut POINT) }; - let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position = phy_point.to_logical(&window_state.window_info()); + + // Screen-absolute position (pt is already in screen coordinates) + let screen_phy_point = PhyPoint::new(pt.x, pt.y); + self.drag_screen_position = screen_phy_point.to_logical(&window_state.window_info()); + + // Window-relative position (convert from screen to client) + let mut window_pt = POINT { x: pt.x, y: pt.y }; + unsafe { ScreenToClient(window_state.hwnd, &mut window_pt as *mut POINT) }; + let window_phy_point = PhyPoint::new(window_pt.x, window_pt.y); + self.drag_position = window_phy_point.to_logical(&window_state.window_info()); } fn parse_drop_data(&mut self, data_object: &IDataObject) { @@ -213,6 +221,7 @@ impl DropTarget { let event = MouseEvent::DragEntered { position: drop_target.drag_position, + screen_position: drop_target.drag_screen_position, modifiers, data: drop_target.drop_data.clone(), }; @@ -237,6 +246,7 @@ impl DropTarget { let event = MouseEvent::DragMoved { position: drop_target.drag_position, + screen_position: drop_target.drag_screen_position, modifiers, data: drop_target.drop_data.clone(), }; @@ -269,6 +279,7 @@ impl DropTarget { let event = MouseEvent::DragDropped { position: drop_target.drag_position, + screen_position: drop_target.drag_screen_position, modifiers, data: drop_target.drop_data.clone(), }; diff --git a/src/win/window.rs b/src/win/window.rs index ac7824b9..7e7e44d6 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -196,13 +196,27 @@ unsafe fn wnd_proc_inner( .on_event(&mut window, enter_event); } + // Window-relative position (from lparam) let x = (lparam & 0xFFFF) as i16 as i32; let y = ((lparam >> 16) & 0xFFFF) as i16 as i32; let physical_pos = PhyPoint { x, y }; let logical_pos = physical_pos.to_logical(&window_state.window_info.borrow()); + + // Screen-absolute position (using GetCursorPos) + let mut screen_physical_pos = POINT { x: 0, y: 0 }; + unsafe { + winapi::um::winuser::GetCursorPos(&mut screen_physical_pos); + } + let screen_physical = PhyPoint { + x: screen_physical_pos.x, + y: screen_physical_pos.y + }; + let screen_logical_pos = screen_physical.to_logical(&window_state.window_info.borrow()); + let move_event = Event::Mouse(MouseEvent::CursorMoved { position: logical_pos, + screen_position: screen_logical_pos, modifiers: window_state .keyboard_state .borrow() diff --git a/src/x11/event_loop.rs b/src/x11/event_loop.rs index 6b6ecd30..e6b97f39 100644 --- a/src/x11/event_loop.rs +++ b/src/x11/event_loop.rs @@ -180,10 +180,14 @@ impl EventLoop { let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); let logical_pos = physical_pos.to_logical(&self.window.window_info); + let screen_physical_pos = PhyPoint::new(event.root_x as i32, event.root_y as i32); + let screen_logical_pos = screen_physical_pos.to_logical(&self.window.window_info); + self.handler.on_event( &mut crate::Window::new(Window { inner: &self.window }), Event::Mouse(MouseEvent::CursorMoved { position: logical_pos, + screen_position: screen_logical_pos, modifiers: key_mods(event.state), }), ); @@ -198,10 +202,13 @@ impl EventLoop { // we generate a CursorMoved as well, so the mouse position from here isn't lost let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); let logical_pos = physical_pos.to_logical(&self.window.window_info); + let screen_physical_pos = PhyPoint::new(event.root_x as i32, event.root_y as i32); + let screen_logical_pos = screen_physical_pos.to_logical(&self.window.window_info); self.handler.on_event( &mut crate::Window::new(Window { inner: &self.window }), Event::Mouse(MouseEvent::CursorMoved { position: logical_pos, + screen_position: screen_logical_pos, modifiers: key_mods(event.state), }), );