diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamouse.m')
| -rw-r--r-- | contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamouse.m | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamouse.m b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamouse.m new file mode 100644 index 0000000..530ca0c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamouse.m | |||
| @@ -0,0 +1,591 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 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 | // RAW 16x16 transparent GIF | ||
| 48 | static unsigned char cursorBytes[] = { | ||
| 49 | 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, | ||
| 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, | ||
| 51 | 0x01, 0x00, 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, | ||
| 52 | 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, | ||
| 53 | 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B | ||
| 54 | }; | ||
| 55 | |||
| 56 | NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0] | ||
| 57 | length:sizeof(cursorBytes) | ||
| 58 | freeWhenDone:NO]; | ||
| 59 | NSImage *cursorImage = [[NSImage alloc] initWithData:cursorData]; | ||
| 60 | invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage | ||
| 61 | hotSpot:NSZeroPoint]; | ||
| 62 | } | ||
| 63 | |||
| 64 | return invisibleCursor; | ||
| 65 | } | ||
| 66 | @end | ||
| 67 | |||
| 68 | static SDL_Cursor *Cocoa_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) | ||
| 69 | { | ||
| 70 | @autoreleasepool { | ||
| 71 | NSImage *nsimage; | ||
| 72 | NSCursor *nscursor = NULL; | ||
| 73 | SDL_Cursor *cursor = NULL; | ||
| 74 | |||
| 75 | nsimage = Cocoa_CreateImage(surface); | ||
| 76 | if (nsimage) { | ||
| 77 | nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(hot_x, hot_y)]; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (nscursor) { | ||
| 81 | cursor = SDL_calloc(1, sizeof(*cursor)); | ||
| 82 | if (cursor) { | ||
| 83 | cursor->internal = (void *)CFBridgingRetain(nscursor); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | return cursor; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | /* there are .pdf files of some of the cursors we need, installed by default on macOS, but not available through NSCursor. | ||
| 92 | If we can load them ourselves, use them, otherwise fallback to something standard but not super-great. | ||
| 93 | Since these are under /System, they should be available even to sandboxed apps. */ | ||
| 94 | static NSCursor *LoadHiddenSystemCursor(NSString *cursorName, SEL fallback) | ||
| 95 | { | ||
| 96 | NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:cursorName]; | ||
| 97 | NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]]; | ||
| 98 | // we can't do animation atm. :/ | ||
| 99 | const int frames = (int)[[info valueForKey:@"frames"] integerValue]; | ||
| 100 | NSCursor *cursor; | ||
| 101 | NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]]; | ||
| 102 | if ((image == nil) || (image.isValid == NO)) { | ||
| 103 | return [NSCursor performSelector:fallback]; | ||
| 104 | } | ||
| 105 | |||
| 106 | if (frames > 1) { | ||
| 107 | #ifdef MAC_OS_VERSION_12_0 // same value as deprecated symbol. | ||
| 108 | const NSCompositingOperation operation = NSCompositingOperationCopy; | ||
| 109 | #else | ||
| 110 | const NSCompositingOperation operation = NSCompositeCopy; | ||
| 111 | #endif | ||
| 112 | const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames)); | ||
| 113 | NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size]; | ||
| 114 | if (cropped == nil) { | ||
| 115 | return [NSCursor performSelector:fallback]; | ||
| 116 | } | ||
| 117 | |||
| 118 | [cropped lockFocus]; | ||
| 119 | { | ||
| 120 | const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height); | ||
| 121 | [image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1]; | ||
| 122 | } | ||
| 123 | [cropped unlockFocus]; | ||
| 124 | image = cropped; | ||
| 125 | } | ||
| 126 | |||
| 127 | cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])]; | ||
| 128 | return cursor; | ||
| 129 | } | ||
| 130 | |||
| 131 | static SDL_Cursor *Cocoa_CreateSystemCursor(SDL_SystemCursor id) | ||
| 132 | { | ||
| 133 | @autoreleasepool { | ||
| 134 | NSCursor *nscursor = NULL; | ||
| 135 | SDL_Cursor *cursor = NULL; | ||
| 136 | |||
| 137 | switch (id) { | ||
| 138 | case SDL_SYSTEM_CURSOR_DEFAULT: | ||
| 139 | nscursor = [NSCursor arrowCursor]; | ||
| 140 | break; | ||
| 141 | case SDL_SYSTEM_CURSOR_TEXT: | ||
| 142 | nscursor = [NSCursor IBeamCursor]; | ||
| 143 | break; | ||
| 144 | case SDL_SYSTEM_CURSOR_CROSSHAIR: | ||
| 145 | nscursor = [NSCursor crosshairCursor]; | ||
| 146 | break; | ||
| 147 | case SDL_SYSTEM_CURSOR_WAIT: // !!! FIXME: this is more like WAITARROW | ||
| 148 | nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); | ||
| 149 | break; | ||
| 150 | case SDL_SYSTEM_CURSOR_PROGRESS: // !!! FIXME: this is meant to be animated | ||
| 151 | nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); | ||
| 152 | break; | ||
| 153 | case SDL_SYSTEM_CURSOR_NWSE_RESIZE: | ||
| 154 | nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); | ||
| 155 | break; | ||
| 156 | case SDL_SYSTEM_CURSOR_NESW_RESIZE: | ||
| 157 | nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); | ||
| 158 | break; | ||
| 159 | case SDL_SYSTEM_CURSOR_EW_RESIZE: | ||
| 160 | nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); | ||
| 161 | break; | ||
| 162 | case SDL_SYSTEM_CURSOR_NS_RESIZE: | ||
| 163 | nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); | ||
| 164 | break; | ||
| 165 | case SDL_SYSTEM_CURSOR_MOVE: | ||
| 166 | nscursor = LoadHiddenSystemCursor(@"move", @selector(closedHandCursor)); | ||
| 167 | break; | ||
| 168 | case SDL_SYSTEM_CURSOR_NOT_ALLOWED: | ||
| 169 | nscursor = [NSCursor operationNotAllowedCursor]; | ||
| 170 | break; | ||
| 171 | case SDL_SYSTEM_CURSOR_POINTER: | ||
| 172 | nscursor = [NSCursor pointingHandCursor]; | ||
| 173 | break; | ||
| 174 | case SDL_SYSTEM_CURSOR_NW_RESIZE: | ||
| 175 | nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); | ||
| 176 | break; | ||
| 177 | case SDL_SYSTEM_CURSOR_N_RESIZE: | ||
| 178 | nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); | ||
| 179 | break; | ||
| 180 | case SDL_SYSTEM_CURSOR_NE_RESIZE: | ||
| 181 | nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); | ||
| 182 | break; | ||
| 183 | case SDL_SYSTEM_CURSOR_E_RESIZE: | ||
| 184 | nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); | ||
| 185 | break; | ||
| 186 | case SDL_SYSTEM_CURSOR_SE_RESIZE: | ||
| 187 | nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); | ||
| 188 | break; | ||
| 189 | case SDL_SYSTEM_CURSOR_S_RESIZE: | ||
| 190 | nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); | ||
| 191 | break; | ||
| 192 | case SDL_SYSTEM_CURSOR_SW_RESIZE: | ||
| 193 | nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); | ||
| 194 | break; | ||
| 195 | case SDL_SYSTEM_CURSOR_W_RESIZE: | ||
| 196 | nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); | ||
| 197 | break; | ||
| 198 | default: | ||
| 199 | SDL_assert(!"Unknown system cursor"); | ||
| 200 | return NULL; | ||
| 201 | } | ||
| 202 | |||
| 203 | if (nscursor) { | ||
| 204 | cursor = SDL_calloc(1, sizeof(*cursor)); | ||
| 205 | if (cursor) { | ||
| 206 | // We'll free it later, so retain it here | ||
| 207 | cursor->internal = (void *)CFBridgingRetain(nscursor); | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | return cursor; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | static SDL_Cursor *Cocoa_CreateDefaultCursor(void) | ||
| 216 | { | ||
| 217 | SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); | ||
| 218 | return Cocoa_CreateSystemCursor(id); | ||
| 219 | } | ||
| 220 | |||
| 221 | static void Cocoa_FreeCursor(SDL_Cursor *cursor) | ||
| 222 | { | ||
| 223 | @autoreleasepool { | ||
| 224 | CFBridgingRelease((void *)cursor->internal); | ||
| 225 | SDL_free(cursor); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | static bool Cocoa_ShowCursor(SDL_Cursor *cursor) | ||
| 230 | { | ||
| 231 | @autoreleasepool { | ||
| 232 | SDL_VideoDevice *device = SDL_GetVideoDevice(); | ||
| 233 | SDL_Window *window = (device ? device->windows : NULL); | ||
| 234 | for (; window != NULL; window = window->next) { | ||
| 235 | SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; | ||
| 236 | if (data) { | ||
| 237 | [data.nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:) | ||
| 238 | withObject:[data.nswindow contentView] | ||
| 239 | waitUntilDone:NO]; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | return true; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | static SDL_Window *SDL_FindWindowAtPoint(const float x, const float y) | ||
| 247 | { | ||
| 248 | const SDL_FPoint pt = { x, y }; | ||
| 249 | SDL_Window *i; | ||
| 250 | for (i = SDL_GetVideoDevice()->windows; i; i = i->next) { | ||
| 251 | const SDL_FRect r = { (float)i->x, (float)i->y, (float)i->w, (float)i->h }; | ||
| 252 | if (SDL_PointInRectFloat(&pt, &r)) { | ||
| 253 | return i; | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | return NULL; | ||
| 258 | } | ||
| 259 | |||
| 260 | static bool Cocoa_WarpMouseGlobal(float x, float y) | ||
| 261 | { | ||
| 262 | CGPoint point; | ||
| 263 | SDL_Mouse *mouse = SDL_GetMouse(); | ||
| 264 | if (mouse->focus) { | ||
| 265 | SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)mouse->focus->internal; | ||
| 266 | if ([data.listener isMovingOrFocusClickPending]) { | ||
| 267 | DLog("Postponing warp, window being moved or focused."); | ||
| 268 | [data.listener setPendingMoveX:x Y:y]; | ||
| 269 | return true; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | point = CGPointMake(x, y); | ||
| 273 | |||
| 274 | Cocoa_HandleMouseWarp(point.x, point.y); | ||
| 275 | |||
| 276 | CGWarpMouseCursorPosition(point); | ||
| 277 | |||
| 278 | /* CGWarpMouse causes a short delay by default, which is preventable by | ||
| 279 | * Calling this directly after. CGSetLocalEventsSuppressionInterval can also | ||
| 280 | * prevent it, but it's deprecated as macOS 10.6. | ||
| 281 | */ | ||
| 282 | if (!mouse->relative_mode) { | ||
| 283 | CGAssociateMouseAndMouseCursorPosition(YES); | ||
| 284 | } | ||
| 285 | |||
| 286 | /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our | ||
| 287 | * other implementations' APIs. Send what's appropriate. | ||
| 288 | */ | ||
| 289 | if (!mouse->relative_mode) { | ||
| 290 | SDL_Window *win = SDL_FindWindowAtPoint(x, y); | ||
| 291 | SDL_SetMouseFocus(win); | ||
| 292 | if (win) { | ||
| 293 | SDL_assert(win == mouse->focus); | ||
| 294 | SDL_SendMouseMotion(0, win, SDL_GLOBAL_MOUSE_ID, false, x - win->x, y - win->y); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | return true; | ||
| 299 | } | ||
| 300 | |||
| 301 | static bool Cocoa_WarpMouse(SDL_Window *window, float x, float y) | ||
| 302 | { | ||
| 303 | return Cocoa_WarpMouseGlobal(window->x + x, window->y + y); | ||
| 304 | } | ||
| 305 | |||
| 306 | static bool Cocoa_SetRelativeMouseMode(bool enabled) | ||
| 307 | { | ||
| 308 | CGError result; | ||
| 309 | |||
| 310 | if (enabled) { | ||
| 311 | SDL_Window *window = SDL_GetKeyboardFocus(); | ||
| 312 | if (window) { | ||
| 313 | /* We will re-apply the relative mode when the window finishes being moved, | ||
| 314 | * if it is being moved right now. | ||
| 315 | */ | ||
| 316 | SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; | ||
| 317 | if ([data.listener isMovingOrFocusClickPending]) { | ||
| 318 | return true; | ||
| 319 | } | ||
| 320 | |||
| 321 | // 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. | ||
| 322 | const CGPoint point = CGPointMake((float)(window->x + (window->w / 2)), (float)(window->y + (window->h / 2))); | ||
| 323 | Cocoa_HandleMouseWarp(point.x, point.y); | ||
| 324 | CGWarpMouseCursorPosition(point); | ||
| 325 | } | ||
| 326 | DLog("Turning on."); | ||
| 327 | result = CGAssociateMouseAndMouseCursorPosition(NO); | ||
| 328 | } else { | ||
| 329 | DLog("Turning off."); | ||
| 330 | result = CGAssociateMouseAndMouseCursorPosition(YES); | ||
| 331 | } | ||
| 332 | if (result != kCGErrorSuccess) { | ||
| 333 | return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed"); | ||
| 334 | } | ||
| 335 | |||
| 336 | /* The hide/unhide calls are redundant most of the time, but they fix | ||
| 337 | * https://bugzilla.libsdl.org/show_bug.cgi?id=2550 | ||
| 338 | */ | ||
| 339 | if (enabled) { | ||
| 340 | [NSCursor hide]; | ||
| 341 | } else { | ||
| 342 | [NSCursor unhide]; | ||
| 343 | } | ||
| 344 | return true; | ||
| 345 | } | ||
| 346 | |||
| 347 | static bool Cocoa_CaptureMouse(SDL_Window *window) | ||
| 348 | { | ||
| 349 | /* our Cocoa event code already tracks the mouse outside the window, | ||
| 350 | so all we have to do here is say "okay" and do what we always do. */ | ||
| 351 | return true; | ||
| 352 | } | ||
| 353 | |||
| 354 | static SDL_MouseButtonFlags Cocoa_GetGlobalMouseState(float *x, float *y) | ||
| 355 | { | ||
| 356 | const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; | ||
| 357 | const NSPoint cocoaLocation = [NSEvent mouseLocation]; | ||
| 358 | SDL_MouseButtonFlags result = 0; | ||
| 359 | |||
| 360 | *x = cocoaLocation.x; | ||
| 361 | *y = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - cocoaLocation.y); | ||
| 362 | |||
| 363 | result |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; | ||
| 364 | result |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; | ||
| 365 | result |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; | ||
| 366 | result |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; | ||
| 367 | result |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; | ||
| 368 | |||
| 369 | return result; | ||
| 370 | } | ||
| 371 | |||
| 372 | bool Cocoa_InitMouse(SDL_VideoDevice *_this) | ||
| 373 | { | ||
| 374 | NSPoint location; | ||
| 375 | SDL_Mouse *mouse = SDL_GetMouse(); | ||
| 376 | SDL_MouseData *internal = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); | ||
| 377 | if (internal == NULL) { | ||
| 378 | return false; | ||
| 379 | } | ||
| 380 | |||
| 381 | mouse->internal = internal; | ||
| 382 | mouse->CreateCursor = Cocoa_CreateCursor; | ||
| 383 | mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; | ||
| 384 | mouse->ShowCursor = Cocoa_ShowCursor; | ||
| 385 | mouse->FreeCursor = Cocoa_FreeCursor; | ||
| 386 | mouse->WarpMouse = Cocoa_WarpMouse; | ||
| 387 | mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; | ||
| 388 | mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; | ||
| 389 | mouse->CaptureMouse = Cocoa_CaptureMouse; | ||
| 390 | mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; | ||
| 391 | |||
| 392 | SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); | ||
| 393 | |||
| 394 | location = [NSEvent mouseLocation]; | ||
| 395 | internal->lastMoveX = location.x; | ||
| 396 | internal->lastMoveY = location.y; | ||
| 397 | return true; | ||
| 398 | } | ||
| 399 | |||
| 400 | static void Cocoa_HandleTitleButtonEvent(SDL_VideoDevice *_this, NSEvent *event) | ||
| 401 | { | ||
| 402 | SDL_Window *window; | ||
| 403 | NSWindow *nswindow = [event window]; | ||
| 404 | |||
| 405 | /* You might land in this function before SDL_Init if showing a message box. | ||
| 406 | Don't dereference a NULL pointer if that happens. */ | ||
| 407 | if (_this == NULL) { | ||
| 408 | return; | ||
| 409 | } | ||
| 410 | |||
| 411 | for (window = _this->windows; window; window = window->next) { | ||
| 412 | SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; | ||
| 413 | if (data && data.nswindow == nswindow) { | ||
| 414 | switch ([event type]) { | ||
| 415 | case NSEventTypeLeftMouseDown: | ||
| 416 | case NSEventTypeRightMouseDown: | ||
| 417 | case NSEventTypeOtherMouseDown: | ||
| 418 | [data.listener setFocusClickPending:[event buttonNumber]]; | ||
| 419 | break; | ||
| 420 | case NSEventTypeLeftMouseUp: | ||
| 421 | case NSEventTypeRightMouseUp: | ||
| 422 | case NSEventTypeOtherMouseUp: | ||
| 423 | [data.listener clearFocusClickPending:[event buttonNumber]]; | ||
| 424 | break; | ||
| 425 | default: | ||
| 426 | break; | ||
| 427 | } | ||
| 428 | break; | ||
| 429 | } | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | static NSWindow *Cocoa_MouseFocus; | ||
| 434 | |||
| 435 | NSWindow *Cocoa_GetMouseFocus() | ||
| 436 | { | ||
| 437 | return Cocoa_MouseFocus; | ||
| 438 | } | ||
| 439 | |||
| 440 | void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) | ||
| 441 | { | ||
| 442 | SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; | ||
| 443 | SDL_Mouse *mouse; | ||
| 444 | SDL_MouseData *data; | ||
| 445 | NSPoint location; | ||
| 446 | CGFloat lastMoveX, lastMoveY; | ||
| 447 | float deltaX, deltaY; | ||
| 448 | bool seenWarp; | ||
| 449 | |||
| 450 | // All events except NSEventTypeMouseExited can only happen if the window | ||
| 451 | // has mouse focus, so we'll always set the focus even if we happen to miss | ||
| 452 | // NSEventTypeMouseEntered, which apparently happens if the window is | ||
| 453 | // created under the mouse on macOS 12.7 | ||
| 454 | NSEventType event_type = [event type]; | ||
| 455 | if (event_type == NSEventTypeMouseExited) { | ||
| 456 | Cocoa_MouseFocus = NULL; | ||
| 457 | } else { | ||
| 458 | Cocoa_MouseFocus = [event window]; | ||
| 459 | } | ||
| 460 | |||
| 461 | switch (event_type) { | ||
| 462 | case NSEventTypeMouseEntered: | ||
| 463 | case NSEventTypeMouseExited: | ||
| 464 | // Focus is handled above | ||
| 465 | return; | ||
| 466 | |||
| 467 | case NSEventTypeMouseMoved: | ||
| 468 | case NSEventTypeLeftMouseDragged: | ||
| 469 | case NSEventTypeRightMouseDragged: | ||
| 470 | case NSEventTypeOtherMouseDragged: | ||
| 471 | break; | ||
| 472 | |||
| 473 | case NSEventTypeLeftMouseDown: | ||
| 474 | case NSEventTypeLeftMouseUp: | ||
| 475 | case NSEventTypeRightMouseDown: | ||
| 476 | case NSEventTypeRightMouseUp: | ||
| 477 | case NSEventTypeOtherMouseDown: | ||
| 478 | case NSEventTypeOtherMouseUp: | ||
| 479 | if ([event window]) { | ||
| 480 | NSRect windowRect = [[[event window] contentView] frame]; | ||
| 481 | if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { | ||
| 482 | Cocoa_HandleTitleButtonEvent(_this, event); | ||
| 483 | return; | ||
| 484 | } | ||
| 485 | } | ||
| 486 | return; | ||
| 487 | |||
| 488 | default: | ||
| 489 | // Ignore any other events. | ||
| 490 | return; | ||
| 491 | } | ||
| 492 | |||
| 493 | mouse = SDL_GetMouse(); | ||
| 494 | data = (SDL_MouseData *)mouse->internal; | ||
| 495 | if (!data) { | ||
| 496 | return; // can happen when returning from fullscreen Space on shutdown | ||
| 497 | } | ||
| 498 | |||
| 499 | seenWarp = data->seenWarp; | ||
| 500 | data->seenWarp = NO; | ||
| 501 | |||
| 502 | location = [NSEvent mouseLocation]; | ||
| 503 | lastMoveX = data->lastMoveX; | ||
| 504 | lastMoveY = data->lastMoveY; | ||
| 505 | data->lastMoveX = location.x; | ||
| 506 | data->lastMoveY = location.y; | ||
| 507 | DLog("Last seen mouse: (%g, %g)", location.x, location.y); | ||
| 508 | |||
| 509 | // Non-relative movement is handled in -[SDL3Cocoa_WindowListener mouseMoved:] | ||
| 510 | if (!mouse->relative_mode) { | ||
| 511 | return; | ||
| 512 | } | ||
| 513 | |||
| 514 | // Ignore events that aren't inside the client area (i.e. title bar.) | ||
| 515 | if ([event window]) { | ||
| 516 | NSRect windowRect = [[[event window] contentView] frame]; | ||
| 517 | if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { | ||
| 518 | return; | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | deltaX = [event deltaX]; | ||
| 523 | deltaY = [event deltaY]; | ||
| 524 | |||
| 525 | if (seenWarp) { | ||
| 526 | deltaX += (lastMoveX - data->lastWarpX); | ||
| 527 | deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - data->lastWarpY); | ||
| 528 | |||
| 529 | DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY); | ||
| 530 | } | ||
| 531 | |||
| 532 | SDL_SendMouseMotion(Cocoa_GetEventTimestamp([event timestamp]), mouse->focus, mouseID, true, deltaX, deltaY); | ||
| 533 | } | ||
| 534 | |||
| 535 | void Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event) | ||
| 536 | { | ||
| 537 | SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; | ||
| 538 | SDL_MouseWheelDirection direction; | ||
| 539 | CGFloat x, y; | ||
| 540 | |||
| 541 | x = -[event deltaX]; | ||
| 542 | y = [event deltaY]; | ||
| 543 | direction = SDL_MOUSEWHEEL_NORMAL; | ||
| 544 | |||
| 545 | if ([event isDirectionInvertedFromDevice] == YES) { | ||
| 546 | direction = SDL_MOUSEWHEEL_FLIPPED; | ||
| 547 | } | ||
| 548 | |||
| 549 | /* For discrete scroll events from conventional mice, always send a full tick. | ||
| 550 | For continuous scroll events from trackpads, send fractional deltas for smoother scrolling. */ | ||
| 551 | if (![event hasPreciseScrollingDeltas]) { | ||
| 552 | if (x > 0) { | ||
| 553 | x = SDL_ceil(x); | ||
| 554 | } else if (x < 0) { | ||
| 555 | x = SDL_floor(x); | ||
| 556 | } | ||
| 557 | if (y > 0) { | ||
| 558 | y = SDL_ceil(y); | ||
| 559 | } else if (y < 0) { | ||
| 560 | y = SDL_floor(y); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | SDL_SendMouseWheel(Cocoa_GetEventTimestamp([event timestamp]), window, mouseID, x, y, direction); | ||
| 565 | } | ||
| 566 | |||
| 567 | void Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) | ||
| 568 | { | ||
| 569 | /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, | ||
| 570 | * since it gets included in the next movement event. | ||
| 571 | */ | ||
| 572 | SDL_MouseData *data = (SDL_MouseData *)SDL_GetMouse()->internal; | ||
| 573 | data->lastWarpX = x; | ||
| 574 | data->lastWarpY = y; | ||
| 575 | data->seenWarp = true; | ||
| 576 | |||
| 577 | DLog("(%g, %g)", x, y); | ||
| 578 | } | ||
| 579 | |||
| 580 | void Cocoa_QuitMouse(SDL_VideoDevice *_this) | ||
| 581 | { | ||
| 582 | SDL_Mouse *mouse = SDL_GetMouse(); | ||
| 583 | if (mouse) { | ||
| 584 | if (mouse->internal) { | ||
| 585 | SDL_free(mouse->internal); | ||
| 586 | mouse->internal = NULL; | ||
| 587 | } | ||
| 588 | } | ||
| 589 | } | ||
| 590 | |||
| 591 | #endif // SDL_VIDEO_DRIVER_COCOA | ||
