Atlas - SDL_cocoamouse.m
Home / ext / SDL / src / video / cocoa Lines: 2 | Size: 23130 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_DRIVER_COCOA 24 25#include "SDL_cocoamouse.h" 26#include "SDL_cocoavideo.h" 27 28#include "../../events/SDL_mouse_c.h" 29 30#if 0 31#define DEBUG_COCOAMOUSE 32#endif 33 34#ifdef DEBUG_COCOAMOUSE 35#define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__) 36#else 37#define DLog(...) \ 38 do { \ 39 } while (0) 40#endif 41 42@implementation NSCursor (InvisibleCursor) 43+ (NSCursor *)invisibleCursor 44{ 45 static NSCursor *invisibleCursor = NULL; 46 if (!invisibleCursor) { 47 const int size = 32; 48 NSImage *cursorImage = [[NSImage alloc] initWithSize:NSMakeSize(size, size)]; 49 NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL 50 pixelsWide:size 51 pixelsHigh:size 52 bitsPerSample:8 53 samplesPerPixel:4 54 hasAlpha:YES 55 isPlanar:NO 56 colorSpaceName:NSDeviceRGBColorSpace 57 bytesPerRow:(size * 4) 58 bitsPerPixel:32]; 59 [cursorImage addRepresentation:imgrep]; 60 61 invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage 62 hotSpot:NSZeroPoint]; 63 } 64 65 return invisibleCursor; 66} 67@end 68 69static SDL_Cursor *Cocoa_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) 70{ 71 @autoreleasepool { 72 NSImage *nsimage; 73 NSCursor *nscursor = NULL; 74 SDL_Cursor *cursor = NULL; 75 76 cursor = SDL_calloc(1, sizeof(*cursor)); 77 if (cursor) { 78 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata) + (sizeof(*cdata->frames) * frame_count)); 79 if (!cdata) { 80 SDL_free(cursor); 81 return NULL; 82 } 83 84 cursor->internal = cdata; 85 86 for (int i = 0; i < frame_count; ++i) { 87 nsimage = Cocoa_CreateImage(frames[i].surface); 88 if (nsimage) { 89 nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(hot_x, hot_y)]; 90 } 91 92 if (nscursor) { 93 ++cdata->num_cursors; 94 cdata->frames[i].cursor = (void *)CFBridgingRetain(nscursor); 95 cdata->frames[i].duration = frames[i].duration; 96 } else { 97 for (int j = 0; j < i; ++j) { 98 CFBridgingRelease(cdata->frames[i].cursor); 99 } 100 101 SDL_free(cdata); 102 SDL_free(cursor); 103 cursor = NULL; 104 break; 105 } 106 } 107 108 return cursor; 109 } 110 } 111 112 return NULL; 113} 114 115static SDL_Cursor *Cocoa_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) 116{ 117 SDL_CursorFrameInfo frame = { 118 surface, 0 119 }; 120 121 return Cocoa_CreateAnimatedCursor(&frame, 1, hot_x, hot_y); 122} 123 124/* there are .pdf files of some of the cursors we need, installed by default on macOS, but not available through NSCursor. 125 If we can load them ourselves, use them, otherwise fallback to something standard but not super-great. 126 Since these are under /System, they should be available even to sandboxed apps. */ 127static NSCursor *LoadHiddenSystemCursor(NSString *cursorName, SEL fallback) 128{ 129 NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:cursorName]; 130 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]]; 131 // we can't do animation atm. :/ 132 const int frames = (int)[[info valueForKey:@"frames"] integerValue]; 133 NSCursor *cursor; 134 NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]]; 135 if ((image == nil) || (image.isValid == NO)) { 136 return [NSCursor performSelector:fallback]; 137 } 138 139 if (frames > 1) { 140#ifdef MAC_OS_VERSION_12_0 // same value as deprecated symbol. 141 const NSCompositingOperation operation = NSCompositingOperationCopy; 142#else 143 const NSCompositingOperation operation = NSCompositeCopy; 144#endif 145 const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames)); 146 NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size]; 147 if (cropped == nil) { 148 return [NSCursor performSelector:fallback]; 149 } 150 151 [cropped lockFocus]; 152 { 153 const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height); 154 [image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1]; 155 } 156 [cropped unlockFocus]; 157 image = cropped; 158 } 159 160 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])]; 161 return cursor; 162} 163 164static SDL_Cursor *Cocoa_CreateSystemCursor(SDL_SystemCursor id) 165{ 166 @autoreleasepool { 167 NSCursor *nscursor = NULL; 168 SDL_Cursor *cursor = NULL; 169 170 switch (id) { 171 case SDL_SYSTEM_CURSOR_DEFAULT: 172 nscursor = [NSCursor arrowCursor]; 173 break; 174 case SDL_SYSTEM_CURSOR_TEXT: 175 nscursor = [NSCursor IBeamCursor]; 176 break; 177 case SDL_SYSTEM_CURSOR_CROSSHAIR: 178 nscursor = [NSCursor crosshairCursor]; 179 break; 180 case SDL_SYSTEM_CURSOR_WAIT: // !!! FIXME: this is more like WAITARROW 181 nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); 182 break; 183 case SDL_SYSTEM_CURSOR_PROGRESS: // !!! FIXME: this is meant to be animated 184 nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); 185 break; 186 case SDL_SYSTEM_CURSOR_NWSE_RESIZE: 187 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 188 break; 189 case SDL_SYSTEM_CURSOR_NESW_RESIZE: 190 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 191 break; 192 case SDL_SYSTEM_CURSOR_EW_RESIZE: 193 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 194 break; 195 case SDL_SYSTEM_CURSOR_NS_RESIZE: 196 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 197 break; 198 case SDL_SYSTEM_CURSOR_MOVE: 199 nscursor = LoadHiddenSystemCursor(@"move", @selector(closedHandCursor)); 200 break; 201 case SDL_SYSTEM_CURSOR_NOT_ALLOWED: 202 nscursor = [NSCursor operationNotAllowedCursor]; 203 break; 204 case SDL_SYSTEM_CURSOR_POINTER: 205 nscursor = [NSCursor pointingHandCursor]; 206 break; 207 case SDL_SYSTEM_CURSOR_NW_RESIZE: 208 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 209 break; 210 case SDL_SYSTEM_CURSOR_N_RESIZE: 211 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 212 break; 213 case SDL_SYSTEM_CURSOR_NE_RESIZE: 214 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 215 break; 216 case SDL_SYSTEM_CURSOR_E_RESIZE: 217 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 218 break; 219 case SDL_SYSTEM_CURSOR_SE_RESIZE: 220 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 221 break; 222 case SDL_SYSTEM_CURSOR_S_RESIZE: 223 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 224 break; 225 case SDL_SYSTEM_CURSOR_SW_RESIZE: 226 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 227 break; 228 case SDL_SYSTEM_CURSOR_W_RESIZE: 229 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 230 break; 231 default: 232 SDL_assert(!"Unknown system cursor"); 233 return NULL; 234 } 235 236 if (nscursor) { 237 cursor = SDL_calloc(1, sizeof(*cursor)); 238 if (cursor) { 239 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata) + sizeof(*cdata->frames)); 240 // We'll free it later, so retain it here 241 cursor->internal = cdata; 242 cdata->frames[0].cursor = (void *)CFBridgingRetain(nscursor); 243 cdata->num_cursors = 1; 244 } 245 } 246 247 return cursor; 248 } 249} 250 251static SDL_Cursor *Cocoa_CreateDefaultCursor(void) 252{ 253 SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); 254 return Cocoa_CreateSystemCursor(id); 255} 256 257static void Cocoa_FreeCursor(SDL_Cursor *cursor) 258{ 259 @autoreleasepool { 260 SDL_CursorData *cdata = cursor->internal; 261 if (cdata->frameTimer) { 262 [cdata->frameTimer invalidate]; 263 } 264 for (int i = 0; i < cdata->num_cursors; ++i) { 265 CFBridgingRelease(cdata->frames[i].cursor); 266 } 267 SDL_free(cdata); 268 SDL_free(cursor); 269 } 270} 271 272static bool Cocoa_ShowCursor(SDL_Cursor *cursor) 273{ 274 @autoreleasepool { 275 SDL_VideoDevice *device = SDL_GetVideoDevice(); 276 SDL_Window *window = (device ? device->windows : NULL); 277 278 if (cursor != NULL) { 279 SDL_CursorData *cdata = cursor->internal; 280 cdata->current_frame = 0; 281 if (cdata->frameTimer) { 282 [cdata->frameTimer invalidate]; 283 cdata->frameTimer = nil; 284 } 285 } 286 287 for (; window != NULL; window = window->next) { 288 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 289 if (data) { 290 [data.nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:) 291 withObject:[data.nswindow contentView] 292 waitUntilDone:NO]; 293 } 294 } 295 return true; 296 } 297} 298 299static SDL_Window *SDL_FindWindowAtPoint(const float x, const float y) 300{ 301 const SDL_FPoint pt = { x, y }; 302 SDL_Window *i; 303 for (i = SDL_GetVideoDevice()->windows; i; i = i->next) { 304 const SDL_FRect r = { (float)i->x, (float)i->y, (float)i->w, (float)i->h }; 305 if (SDL_PointInRectFloat(&pt, &r)) { 306 return i; 307 } 308 } 309 310 return NULL; 311} 312 313static bool Cocoa_WarpMouseGlobal(float x, float y) 314{ 315 CGPoint point; 316 SDL_Mouse *mouse = SDL_GetMouse(); 317 if (mouse->focus) { 318 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)mouse->focus->internal; 319 if ([data.listener isMovingOrFocusClickPending]) { 320 DLog("Postponing warp, window being moved or focused."); 321 [data.listener setPendingMoveX:x Y:y]; 322 return true; 323 } 324 } 325 point = CGPointMake(x, y); 326 327 Cocoa_HandleMouseWarp(point.x, point.y); 328 329 CGWarpMouseCursorPosition(point); 330 331 /* CGWarpMouse causes a short delay by default, which is preventable by 332 * Calling this directly after. CGSetLocalEventsSuppressionInterval can also 333 * prevent it, but it's deprecated as macOS 10.6. 334 */ 335 if (!mouse->relative_mode) { 336 CGAssociateMouseAndMouseCursorPosition(YES); 337 } 338 339 /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our 340 * other implementations' APIs. Send what's appropriate. 341 */ 342 if (!mouse->relative_mode) { 343 SDL_Window *win = SDL_FindWindowAtPoint(x, y); 344 SDL_SetMouseFocus(win); 345 if (win) { 346 SDL_assert(win == mouse->focus); 347 SDL_SendMouseMotion(0, win, SDL_GLOBAL_MOUSE_ID, false, x - win->x, y - win->y); 348 } 349 } 350 351 return true; 352} 353 354static bool Cocoa_WarpMouse(SDL_Window *window, float x, float y) 355{ 356 return Cocoa_WarpMouseGlobal(window->x + x, window->y + y); 357} 358 359static bool Cocoa_SetRelativeMouseMode(bool enabled) 360{ 361 CGError result; 362 363 if (enabled) { 364 SDL_Window *window = SDL_GetKeyboardFocus(); 365 if (window) { 366 /* We will re-apply the relative mode when the window finishes being moved, 367 * if it is being moved right now. 368 */ 369 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 370 if ([data.listener isMovingOrFocusClickPending]) { 371 return true; 372 } 373 374 // make sure the mouse isn't at the corner of the window, as this can confuse things if macOS thinks a window resize is happening on the first click. 375 const CGPoint point = CGPointMake((float)(window->x + (window->w / 2)), (float)(window->y + (window->h / 2))); 376 Cocoa_HandleMouseWarp(point.x, point.y); 377 CGWarpMouseCursorPosition(point); 378 } 379 DLog("Turning on."); 380 result = CGAssociateMouseAndMouseCursorPosition(NO); 381 } else { 382 DLog("Turning off."); 383 result = CGAssociateMouseAndMouseCursorPosition(YES); 384 } 385 if (result != kCGErrorSuccess) { 386 return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed"); 387 } 388 389 /* The hide/unhide calls are redundant most of the time, but they fix 390 * https://bugzilla.libsdl.org/show_bug.cgi?id=2550 391 */ 392 if (enabled) { 393 [NSCursor hide]; 394 } else { 395 [NSCursor unhide]; 396 } 397 return true; 398} 399 400static bool Cocoa_CaptureMouse(SDL_Window *window) 401{ 402 /* our Cocoa event code already tracks the mouse outside the window, 403 so all we have to do here is say "okay" and do what we always do. */ 404 return true; 405} 406 407static SDL_MouseButtonFlags Cocoa_GetGlobalMouseState(float *x, float *y) 408{ 409 const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; 410 const NSPoint cocoaLocation = [NSEvent mouseLocation]; 411 SDL_MouseButtonFlags result = 0; 412 413 *x = cocoaLocation.x; 414 *y = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - cocoaLocation.y); 415 416 result |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; 417 result |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; 418 result |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; 419 result |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; 420 result |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; 421 422 return result; 423} 424 425bool Cocoa_InitMouse(SDL_VideoDevice *_this) 426{ 427 NSPoint location; 428 SDL_Mouse *mouse = SDL_GetMouse(); 429 SDL_MouseData *data = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); 430 if (data == NULL) { 431 return false; 432 } 433 434 mouse->internal = data; 435 mouse->CreateCursor = Cocoa_CreateCursor; 436 mouse->CreateAnimatedCursor = Cocoa_CreateAnimatedCursor; 437 mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; 438 mouse->ShowCursor = Cocoa_ShowCursor; 439 mouse->FreeCursor = Cocoa_FreeCursor; 440 mouse->WarpMouse = Cocoa_WarpMouse; 441 mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; 442 mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; 443 mouse->CaptureMouse = Cocoa_CaptureMouse; 444 mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; 445 446 SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); 447 448 location = [NSEvent mouseLocation]; 449 data->lastMoveX = location.x; 450 data->lastMoveY = location.y; 451 return true; 452} 453 454static void Cocoa_HandleTitleButtonEvent(SDL_VideoDevice *_this, NSEvent *event) 455{ 456 SDL_Window *window; 457 NSWindow *nswindow = [event window]; 458 459 /* You might land in this function before SDL_Init if showing a message box. 460 Don't dereference a NULL pointer if that happens. */ 461 if (_this == NULL) { 462 return; 463 } 464 465 for (window = _this->windows; window; window = window->next) { 466 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 467 if (data && data.nswindow == nswindow) { 468 switch ([event type]) { 469 case NSEventTypeLeftMouseDown: 470 case NSEventTypeRightMouseDown: 471 case NSEventTypeOtherMouseDown: 472 [data.listener setFocusClickPending:[event buttonNumber]]; 473 break; 474 case NSEventTypeLeftMouseUp: 475 case NSEventTypeRightMouseUp: 476 case NSEventTypeOtherMouseUp: 477 [data.listener clearFocusClickPending:[event buttonNumber]]; 478 break; 479 default: 480 break; 481 } 482 break; 483 } 484 } 485} 486 487static NSWindow *Cocoa_MouseFocus; 488 489NSWindow *Cocoa_GetMouseFocus() 490{ 491 return Cocoa_MouseFocus; 492} 493 494static void Cocoa_ReconcileButtonState(NSEvent *event) 495{ 496 // Send mouse up events for any buttons that are no longer pressed 497 Uint32 buttons = SDL_GetMouseState(NULL, NULL); 498 if (buttons && ![NSEvent pressedMouseButtons]) { 499 Uint8 button = SDL_BUTTON_LEFT; 500 while (buttons) { 501 if (buttons & 0x01) { 502 SDL_SendMouseButton(Cocoa_GetEventTimestamp([event timestamp]), SDL_GetMouseFocus(), SDL_GLOBAL_MOUSE_ID, button, false); 503 } 504 ++button; 505 buttons >>= 1; 506 } 507 } 508} 509 510void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) 511{ 512 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 513 SDL_Mouse *mouse; 514 SDL_MouseData *data; 515 NSPoint location; 516 CGFloat lastMoveX, lastMoveY; 517 float deltaX, deltaY; 518 bool seenWarp; 519 520 // All events except NSEventTypeMouseExited can only happen if the window 521 // has mouse focus, so we'll always set the focus even if we happen to miss 522 // NSEventTypeMouseEntered, which apparently happens if the window is 523 // created under the mouse on macOS 12.7. But, only set the focus if 524 // the event acutally has a non-NULL window, otherwise what would happen 525 // is that after an NSEventTypeMouseEntered there would sometimes be 526 // NSEventTypeMouseMoved without a window causing us to suppress subsequent 527 // mouse move events. 528 NSEventType event_type = [event type]; 529 if (event_type == NSEventTypeMouseExited) { 530 Cocoa_MouseFocus = NULL; 531 } else { 532 if ([event window] != NULL) { 533 Cocoa_MouseFocus = [event window]; 534 Cocoa_ReconcileButtonState(event); 535 } 536 } 537 538 switch (event_type) { 539 case NSEventTypeMouseEntered: 540 case NSEventTypeMouseExited: 541 // Focus is handled above 542 return; 543 544 case NSEventTypeMouseMoved: 545 case NSEventTypeLeftMouseDragged: 546 case NSEventTypeRightMouseDragged: 547 case NSEventTypeOtherMouseDragged: 548 break; 549 550 case NSEventTypeLeftMouseDown: 551 case NSEventTypeLeftMouseUp: 552 case NSEventTypeRightMouseDown: 553 case NSEventTypeRightMouseUp: 554 case NSEventTypeOtherMouseDown: 555 case NSEventTypeOtherMouseUp: 556 if ([event window]) { 557 NSRect windowRect = [[[event window] contentView] frame]; 558 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 559 Cocoa_HandleTitleButtonEvent(_this, event); 560 return; 561 } 562 } 563 return; 564 565 default: 566 // Ignore any other events. 567 return; 568 } 569 570 mouse = SDL_GetMouse(); 571 data = (SDL_MouseData *)mouse->internal; 572 if (!data) { 573 return; // can happen when returning from fullscreen Space on shutdown 574 } 575 576 seenWarp = data->seenWarp; 577 data->seenWarp = NO; 578 579 location = [NSEvent mouseLocation]; 580 lastMoveX = data->lastMoveX; 581 lastMoveY = data->lastMoveY; 582 data->lastMoveX = location.x; 583 data->lastMoveY = location.y; 584 DLog("Last seen mouse: (%g, %g)", location.x, location.y); 585 586 // Non-relative movement is handled in -[SDL3Cocoa_WindowListener mouseMoved:] 587 if (!mouse->relative_mode) { 588 return; 589 } 590 591 // Ignore events that aren't inside the client area (i.e. title bar.) 592 if ([event window]) { 593 NSRect windowRect = [[[event window] contentView] frame]; 594 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 595 return; 596 } 597 } 598 599 deltaX = [event deltaX]; 600 deltaY = [event deltaY]; 601 602 if (seenWarp) { 603 deltaX += (lastMoveX - data->lastWarpX); 604 deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - data->lastWarpY); 605 606 DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY); 607 } 608 609 SDL_SendMouseMotion(Cocoa_GetEventTimestamp([event timestamp]), mouse->focus, mouseID, true, deltaX, deltaY); 610} 611 612void Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event) 613{ 614 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 615 SDL_MouseWheelDirection direction; 616 CGFloat x, y; 617 618 x = -[event scrollingDeltaX]; 619 y = [event scrollingDeltaY]; 620 direction = SDL_MOUSEWHEEL_NORMAL; 621 622 if ([event isDirectionInvertedFromDevice] == YES) { 623 direction = SDL_MOUSEWHEEL_FLIPPED; 624 } 625 626 if ([event hasPreciseScrollingDeltas]) { 627 x *= 0.1; 628 y *= 0.1; 629 } 630 631 SDL_SendMouseWheel(Cocoa_GetEventTimestamp([event timestamp]), window, mouseID, x, y, direction); 632} 633 634void Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) 635{ 636 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, 637 * since it gets included in the next movement event. 638 */ 639 SDL_MouseData *data = (SDL_MouseData *)SDL_GetMouse()->internal; 640 data->lastWarpX = x; 641 data->lastWarpY = y; 642 data->seenWarp = true; 643 644 DLog("(%g, %g)", x, y); 645} 646 647void Cocoa_QuitMouse(SDL_VideoDevice *_this) 648{ 649} 650 651#endif // SDL_VIDEO_DRIVER_COCOA 652[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.