Mercurial > sdl-ios-xcode
comparison src/video/quartz/SDL_QuartzEvents.m @ 563:04dcaf3da918
Massive Quartz input enhancements from Darrell Walisser. His email:
Enclosed is a patch that addresses the following:
--Various minor cleanups.
Removed dead/obsolete code, made some style cleanups
--Mouse Events
Now keep track of what button(s) were pressed so we know when to send
the mouse up event. This fixes the case where the mouse is dragged
outside of the game window and released (in which case we want to send
the mouse up event even though the mouse is outside the game window).
--Input Grabbing
Here is my take on the grabbing situation, which is the basis for the
new implementation.
There are 3 grab states, ungrabbed (UG), visible (VG), and invisible
(IG). Both VG and IG keep the mouse constrained to the window and
produce relative motion events. In VG the cursor is visible (duh), in
IG it is not. In VG, absolute motion events also work.
There are 6 actions that can affect grabbing:
1. Set Fullscreen/Window (F/W). In fullscreen, a visible grab should do
nothing. However, a fullscreen visible grab can be treated just like a
windowed visible grab, which is what I have done to help simplify
things.
2. Cursor hide/show (H/S). If the cursor is hidden when grabbing, the
grab is an invisible grab. If the cursor is visible, the grab should
just constrain the mouse to the window.
3. Input grab/ungrab(G/U). If grabbed, the cursor should be confined to
the window as should the keyboard input. On Mac OS X, the keyboard
input is implicitly grabbed by confining the cursor, except for
command-tab which can switch away from the application. Should the
window come to the foreground if the application is deactivated and
grab input is called? This isn't necessary in this implementation
because the grab state will be asserted upon activation.
Using my notation, these are all the cases that need to be handled
(state + action = new state).
UG+U = UG
UG+G = VG or IG, if cursor is visible or not
UG+H = UG
UG+S = UG
VG+U = UG
VG+G = VG
VG+H = IG
VG+S = VG
IG+U = UG
IG+G = IG
IG+H = IG
IG+S = VG
The cases that result in the same state can be ignored in the code,
which cuts it down to just 5 cases.
Another issue is what happens when the app loses/gains input focus from
deactivate/activate or iconify/deiconify. I think that if input focus
is ever lost (outside of SDL's control), the grab state should be
suspended and the cursor should become visible and active again. When
regained, the cursor should reappear in its original location and/or
grab state. This way, when reactivating the cursor is still in the same
position as before so apps shouldn't get confused when the next motion
event comes in. This is what I've done in this patch.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Fri, 27 Dec 2002 20:52:41 +0000 |
parents | 4bcf7dd06c47 |
children | 7ec821f3cbd0 |
comparison
equal
deleted
inserted
replaced
562:cb40b26523a5 | 563:04dcaf3da918 |
---|---|
298 } | 298 } |
299 | 299 |
300 static void QZ_DoActivate (_THIS) | 300 static void QZ_DoActivate (_THIS) |
301 { | 301 { |
302 in_foreground = YES; | 302 in_foreground = YES; |
303 | 303 |
304 /* Regrab the mouse, only if it was previously grabbed */ | 304 /* Hide the mouse cursor if was hidden */ |
305 if (!cursor_visible) { | |
306 HideCursor (); | |
307 } | |
308 | |
309 /* Regrab input, only if it was previously grabbed */ | |
305 if ( current_grab_mode == SDL_GRAB_ON ) { | 310 if ( current_grab_mode == SDL_GRAB_ON ) { |
306 QZ_WarpWMCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2); | 311 |
307 CGAssociateMouseAndMouseCursorPosition (0); | 312 /* Restore cursor location if input was grabbed */ |
308 } | 313 QZ_PrivateWarpCursor (this, cursor_loc.x, cursor_loc.y); |
309 | 314 QZ_ChangeGrabState (this, QZ_ENABLE_GRAB); |
310 /* Hide the mouse cursor if inside the app window */ | |
311 if (!QZ_cursor_visible) { | |
312 HideCursor (); | |
313 } | 315 } |
314 | 316 |
315 SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS); | 317 SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS); |
316 } | 318 } |
317 | 319 |
318 static void QZ_DoDeactivate (_THIS) { | 320 static void QZ_DoDeactivate (_THIS) { |
319 | 321 |
320 in_foreground = NO; | 322 in_foreground = NO; |
321 | 323 |
322 /* Ungrab mouse if it is grabbed */ | 324 /* Get the current cursor location, for restore on activate */ |
323 if ( current_grab_mode == SDL_GRAB_ON ) { | 325 cursor_loc = [ NSEvent mouseLocation ]; /* global coordinates */ |
324 CGAssociateMouseAndMouseCursorPosition (1); | 326 if (qz_window) |
325 } | 327 QZ_PrivateGlobalToLocal (this, &cursor_loc); |
326 | 328 QZ_PrivateCocoaToSDL (this, &cursor_loc); |
327 /* Show the mouse cursor */ | 329 |
328 if (!QZ_cursor_visible) { | 330 /* Reassociate mouse and cursor */ |
329 ShowCursor (); | 331 CGAssociateMouseAndMouseCursorPosition (1); |
330 } | 332 |
333 /* Show the cursor */ | |
334 ShowCursor (); | |
331 | 335 |
332 SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS); | 336 SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS); |
333 } | 337 } |
334 | 338 |
335 void QZ_SleepNotificationHandler (void * refcon, | 339 void QZ_SleepNotificationHandler (void * refcon, |
340 SDL_VideoDevice *this = (SDL_VideoDevice*)refcon; | 344 SDL_VideoDevice *this = (SDL_VideoDevice*)refcon; |
341 | 345 |
342 switch(messageType) | 346 switch(messageType) |
343 { | 347 { |
344 case kIOMessageSystemWillSleep: | 348 case kIOMessageSystemWillSleep: |
345 IOAllowPowerChange(powerConnection, (long) messageArgument); | 349 IOAllowPowerChange(power_connection, (long) messageArgument); |
346 break; | 350 break; |
347 case kIOMessageCanSystemSleep: | 351 case kIOMessageCanSystemSleep: |
348 IOAllowPowerChange(powerConnection, (long) messageArgument); | 352 IOAllowPowerChange(power_connection, (long) messageArgument); |
349 break; | 353 break; |
350 case kIOMessageSystemHasPoweredOn: | 354 case kIOMessageSystemHasPoweredOn: |
351 /* awake */ | 355 /* awake */ |
352 SDL_PrivateExpose(); | 356 SDL_PrivateExpose(); |
353 break; | 357 break; |
358 { | 362 { |
359 CFRunLoopSourceRef rls; | 363 CFRunLoopSourceRef rls; |
360 IONotificationPortRef thePortRef; | 364 IONotificationPortRef thePortRef; |
361 io_object_t notifier; | 365 io_object_t notifier; |
362 | 366 |
363 powerConnection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, ¬ifier); | 367 power_connection = IORegisterForSystemPower (this, &thePortRef, QZ_SleepNotificationHandler, ¬ifier); |
364 | 368 |
365 if (powerConnection == 0) | 369 if (power_connection == 0) |
366 NSLog(@"SDL: QZ_SleepNotificationHandler() IORegisterForSystemPower failed."); | 370 NSLog(@"SDL: QZ_SleepNotificationHandler() IORegisterForSystemPower failed."); |
367 | 371 |
368 rls = IONotificationPortGetRunLoopSource (thePortRef); | 372 rls = IONotificationPortGetRunLoopSource (thePortRef); |
369 CFRunLoopAddSource (CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); | 373 CFRunLoopAddSource (CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
370 CFRelease (rls); | 374 CFRelease (rls); |
399 | 403 |
400 /* Poll for an event. This will not block */ | 404 /* Poll for an event. This will not block */ |
401 event = [ NSApp nextEventMatchingMask:NSAnyEventMask | 405 event = [ NSApp nextEventMatchingMask:NSAnyEventMask |
402 untilDate:distantPast | 406 untilDate:distantPast |
403 inMode: NSDefaultRunLoopMode dequeue:YES ]; | 407 inMode: NSDefaultRunLoopMode dequeue:YES ]; |
404 | |
405 if (event != nil) { | 408 if (event != nil) { |
406 | 409 |
407 unsigned int type; | 410 unsigned int type; |
408 BOOL isForGameWin; | 411 BOOL isForGameWin; |
409 | 412 BOOL isInGameWin; |
410 #define DO_MOUSE_DOWN(button, sendToWindow) do { \ | 413 |
414 #define DO_MOUSE_DOWN(button) do { \ | |
411 if ( in_foreground ) { \ | 415 if ( in_foreground ) { \ |
412 if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \ | 416 if ( isInGameWin ) { \ |
413 NSPointInRect([event locationInWindow], winRect) ) \ | 417 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \ |
414 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); \ | 418 expect_mouse_up |= 1<<button; \ |
419 } \ | |
415 } \ | 420 } \ |
416 else { \ | 421 else { \ |
417 QZ_DoActivate (this); \ | 422 QZ_DoActivate (this); \ |
418 } \ | 423 } \ |
419 [ NSApp sendEvent:event ]; \ | 424 [ NSApp sendEvent:event ]; \ |
420 } while(0) | 425 } while(0) |
421 | 426 |
422 #define DO_MOUSE_UP(button, sendToWindow) do { \ | 427 #define DO_MOUSE_UP(button) do { \ |
423 if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) || \ | 428 if ( expect_mouse_up & (1<<button) ) { \ |
424 !NSPointInRect([event locationInWindow], titleBarRect) ) \ | 429 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \ |
425 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); \ | 430 expect_mouse_up &= ~(1<<button); \ |
426 [ NSApp sendEvent:event ]; \ | 431 } \ |
432 [ NSApp sendEvent:event ]; \ | |
427 } while(0) | 433 } while(0) |
428 | 434 |
429 type = [ event type ]; | 435 type = [ event type ]; |
430 isForGameWin = (qz_window == [ event window ]); | 436 isForGameWin = (qz_window == [ event window ]); |
437 isInGameWin = NSPointInRect([event locationInWindow], winRect); | |
431 switch (type) { | 438 switch (type) { |
432 | |
433 case NSLeftMouseDown: | 439 case NSLeftMouseDown: |
434 if ( getenv("SDL_HAS3BUTTONMOUSE") ) { | 440 if ( getenv("SDL_HAS3BUTTONMOUSE") ) { |
435 DO_MOUSE_DOWN (1, 1); | 441 DO_MOUSE_DOWN (SDL_BUTTON_LEFT); |
436 } else { | 442 } else { |
437 if ( NSCommandKeyMask & current_mods ) { | 443 if ( NSCommandKeyMask & current_mods ) { |
438 last_virtual_button = 3; | 444 last_virtual_button = SDL_BUTTON_RIGHT; |
439 DO_MOUSE_DOWN (3, 0); | 445 DO_MOUSE_DOWN (SDL_BUTTON_RIGHT); |
440 } | 446 } |
441 else if ( NSAlternateKeyMask & current_mods ) { | 447 else if ( NSAlternateKeyMask & current_mods ) { |
442 last_virtual_button = 2; | 448 last_virtual_button = SDL_BUTTON_MIDDLE; |
443 DO_MOUSE_DOWN (2, 0); | 449 DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE); |
444 } | 450 } |
445 else { | 451 else { |
446 DO_MOUSE_DOWN (1, 1); | 452 DO_MOUSE_DOWN (SDL_BUTTON_LEFT); |
447 } | 453 } |
448 } | 454 } |
449 break; | 455 break; |
450 case NSOtherMouseDown: DO_MOUSE_DOWN (2, 0); break; | 456 case NSOtherMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_MIDDLE); break; |
451 case NSRightMouseDown: DO_MOUSE_DOWN (3, 0); break; | 457 case NSRightMouseDown: DO_MOUSE_DOWN (SDL_BUTTON_RIGHT); break; |
452 case NSLeftMouseUp: | 458 case NSLeftMouseUp: |
453 if ( last_virtual_button != 0 ) { | 459 if ( last_virtual_button != 0 ) { |
454 DO_MOUSE_UP (last_virtual_button, 0); | 460 DO_MOUSE_UP (last_virtual_button); |
455 last_virtual_button = 0; | 461 last_virtual_button = 0; |
456 } | 462 } |
457 else { | 463 else { |
458 DO_MOUSE_UP (1, 1); | 464 DO_MOUSE_UP (SDL_BUTTON_LEFT); |
459 } | 465 } |
460 break; | 466 break; |
461 case NSOtherMouseUp: DO_MOUSE_UP (2, 0); break; | 467 case NSOtherMouseUp: DO_MOUSE_UP (SDL_BUTTON_MIDDLE); break; |
462 case NSRightMouseUp: DO_MOUSE_UP (3, 0); break; | 468 case NSRightMouseUp: DO_MOUSE_UP (SDL_BUTTON_RIGHT); break; |
463 case NSSystemDefined: | 469 case NSSystemDefined: |
464 /* | 470 /* |
465 Future: up to 32 "mouse" buttons can be handled. | 471 Future: up to 32 "mouse" buttons can be handled. |
466 if ([event subtype] == 7) { | 472 if ([event subtype] == 7) { |
467 unsigned int buttons; | 473 unsigned int buttons; |
470 break; | 476 break; |
471 case NSLeftMouseDragged: | 477 case NSLeftMouseDragged: |
472 case NSRightMouseDragged: | 478 case NSRightMouseDragged: |
473 case NSOtherMouseDragged: /* usually middle mouse dragged */ | 479 case NSOtherMouseDragged: /* usually middle mouse dragged */ |
474 case NSMouseMoved: | 480 case NSMouseMoved: |
475 if (current_grab_mode == SDL_GRAB_ON) { | 481 if ( grab_state == QZ_INVISIBLE_GRAB ) { |
476 | 482 |
477 /* | 483 /* |
478 If input is grabbed, the cursor doesn't move, | 484 If input is grabbed+hidden, the cursor doesn't move, |
479 so we have to call the lowlevel window server | 485 so we have to call the lowlevel window server |
480 function. This is less accurate but works OK. | 486 function. This is less accurate but works OK. |
481 */ | 487 */ |
482 CGMouseDelta dx1, dy1; | 488 CGMouseDelta dx1, dy1; |
483 CGGetLastMouseDelta (&dx1, &dy1); | 489 CGGetLastMouseDelta (&dx1, &dy1); |
484 dx += dx1; | 490 dx += dx1; |
485 dy += dy1; | 491 dy += dy1; |
486 } | |
487 else if (warp_flag) { | |
488 | |
489 /* | |
490 If we just warped the mouse, the cursor is frozen for a while. | |
491 So we have to use the lowlevel function until it | |
492 unfreezes. This really helps apps that continuously | |
493 warp the mouse to keep it in the game window. Developers should | |
494 really use GrabInput, but our GrabInput freezes the HW cursor, | |
495 which doesn't cut it for some apps. | |
496 */ | |
497 Uint32 ticks; | |
498 | |
499 ticks = SDL_GetTicks(); | |
500 if (ticks - warp_ticks < 150) { | |
501 | |
502 CGMouseDelta dx1, dy1; | |
503 CGGetLastMouseDelta (&dx1, &dy1); | |
504 dx += dx1; | |
505 dy += dy1; | |
506 } | |
507 else { | |
508 | |
509 warp_flag = 0; | |
510 } | |
511 } | 492 } |
512 else if (firstMouseEvent) { | 493 else if (firstMouseEvent) { |
513 | 494 |
514 /* | 495 /* |
515 Get the first mouse event in a possible | 496 Get the first mouse event in a possible |
518 compensate any inaccuracy in deltas, and | 499 compensate any inaccuracy in deltas, and |
519 provides the first known mouse position, | 500 provides the first known mouse position, |
520 since everything after this uses deltas | 501 since everything after this uses deltas |
521 */ | 502 */ |
522 NSPoint p = [ event locationInWindow ]; | 503 NSPoint p = [ event locationInWindow ]; |
523 QZ_PrivateCocoaToSDL(this, &p); | 504 QZ_PrivateCocoaToSDL (this, &p); |
524 | |
525 SDL_PrivateMouseMotion (0, 0, p.x, p.y); | 505 SDL_PrivateMouseMotion (0, 0, p.x, p.y); |
526 | |
527 firstMouseEvent = 0; | 506 firstMouseEvent = 0; |
528 } | 507 } |
529 else { | 508 else { |
530 | 509 |
531 /* | 510 /* |
533 add it on for one big move event at the end. | 512 add it on for one big move event at the end. |
534 */ | 513 */ |
535 dx += [ event deltaX ]; | 514 dx += [ event deltaX ]; |
536 dy += [ event deltaY ]; | 515 dy += [ event deltaY ]; |
537 } | 516 } |
517 | |
518 /* | |
519 Handle grab input+cursor visible by warping the cursor back | |
520 into the game window. This still generates a mouse moved event, | |
521 but not as a result of the warp (so it's in the right direction). | |
522 */ | |
523 if ( grab_state == QZ_VISIBLE_GRAB && | |
524 !isInGameWin ) { | |
525 | |
526 NSPoint p = [ event locationInWindow ]; | |
527 QZ_PrivateCocoaToSDL (this, &p); | |
528 | |
529 if ( p.x < 0.0 ) | |
530 p.x = 0.0; | |
531 | |
532 if ( p.y < 0.0 ) | |
533 p.y = 0.0; | |
534 | |
535 if ( p.x >= winRect.size.width ) | |
536 p.x = winRect.size.width-1; | |
537 | |
538 if ( p.y >= winRect.size.height ) | |
539 p.y = winRect.size.height-1; | |
540 | |
541 QZ_PrivateWarpCursor (this, p.x, p.y); | |
542 } | |
538 break; | 543 break; |
539 case NSScrollWheel: | 544 case NSScrollWheel: |
540 if (NSPointInRect([ event locationInWindow ], winRect)) { | 545 if ( isInGameWin ) { |
541 float dy; | 546 float dy; |
542 Uint8 button; | 547 Uint8 button; |
543 dy = [ event deltaY ]; | 548 dy = [ event deltaY ]; |
544 if ( dy > 0.0 ) /* Scroll up */ | 549 if ( dy > 0.0 ) /* Scroll up */ |
545 button = SDL_BUTTON_WHEELUP; | 550 button = SDL_BUTTON_WHEELUP; |
546 else /* Scroll down */ | 551 else /* Scroll down */ |
547 button = SDL_BUTTON_WHEELDOWN; | 552 button = SDL_BUTTON_WHEELDOWN; |
548 /* For now, wheel is sent as a quick down+up */ | 553 /* For now, wheel is sent as a quick down+up */ |
549 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); | 554 SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0); |
550 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); | 555 SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0); |
551 } | 556 } |
552 break; | 557 break; |
553 case NSKeyUp: | 558 case NSKeyUp: |