src/video/cocoa/SDL_cocoamouse.m
author Jørgen P. Tjernø <jorgen@valvesoftware.com>
Tue, 25 Feb 2014 17:27:41 -0800
changeset 8260 028ed8da2524
parent 8232 8976fb30952f
child 8261 841b66e4397a
permissions -rw-r--r--
Mac: Improve moving relative mode windows.

This makes it possible to move windows by their title bar, even if they're in
relative mode, if you click the title bar when the window does not have focus.

Bug: https://bugzilla.libsdl.org/show_bug.cgi?id=2396
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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 #if SDL_VIDEO_DRIVER_COCOA
    24 
    25 #include "SDL_assert.h"
    26 #include "SDL_events.h"
    27 #include "SDL_cocoamouse.h"
    28 #include "SDL_cocoamousetap.h"
    29 
    30 #include "../../events/SDL_mouse_c.h"
    31 
    32 /* #define DEBUG_COCOAMOUSE */
    33 
    34 #ifdef DEBUG_COCOAMOUSE
    35 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
    36 #else
    37 #define DLog(...) do { } while (0)
    38 #endif
    39 
    40 @implementation NSCursor (InvisibleCursor)
    41 + (NSCursor *)invisibleCursor
    42 {
    43     static NSCursor *invisibleCursor = NULL;
    44     if (!invisibleCursor) {
    45         /* RAW 16x16 transparent GIF */
    46         static unsigned char cursorBytes[] = {
    47             0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80,
    48             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04,
    49             0x01, 0x00, 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10,
    50             0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED,
    51             0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B
    52         };
    53 
    54         NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0]
    55                                                   length:sizeof(cursorBytes)
    56                                             freeWhenDone:NO];
    57         NSImage *cursorImage = [[[NSImage alloc] initWithData:cursorData] autorelease];
    58         invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage
    59                                                   hotSpot:NSZeroPoint];
    60     }
    61 
    62     return invisibleCursor;
    63 }
    64 @end
    65 
    66 
    67 static SDL_Cursor *
    68 Cocoa_CreateDefaultCursor()
    69 {
    70     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    71     NSCursor *nscursor;
    72     SDL_Cursor *cursor = NULL;
    73 
    74     nscursor = [NSCursor arrowCursor];
    75 
    76     if (nscursor) {
    77         cursor = SDL_calloc(1, sizeof(*cursor));
    78         if (cursor) {
    79             cursor->driverdata = nscursor;
    80             [nscursor retain];
    81         }
    82     }
    83 
    84     [pool release];
    85 
    86     return cursor;
    87 }
    88 
    89 static SDL_Cursor *
    90 Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
    91 {
    92     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    93     NSImage *nsimage;
    94     NSCursor *nscursor = NULL;
    95     SDL_Cursor *cursor = NULL;
    96 
    97     nsimage = Cocoa_CreateImage(surface);
    98     if (nsimage) {
    99         nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)];
   100     }
   101 
   102     if (nscursor) {
   103         cursor = SDL_calloc(1, sizeof(*cursor));
   104         if (cursor) {
   105             cursor->driverdata = nscursor;
   106         } else {
   107             [nscursor release];
   108         }
   109     }
   110 
   111     [pool release];
   112 
   113     return cursor;
   114 }
   115 
   116 static SDL_Cursor *
   117 Cocoa_CreateSystemCursor(SDL_SystemCursor id)
   118 {
   119     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   120     NSCursor *nscursor = NULL;
   121     SDL_Cursor *cursor = NULL;
   122 
   123     switch(id)
   124     {
   125     case SDL_SYSTEM_CURSOR_ARROW:
   126         nscursor = [NSCursor arrowCursor];
   127         break;
   128     case SDL_SYSTEM_CURSOR_IBEAM:
   129         nscursor = [NSCursor IBeamCursor];
   130         break;
   131     case SDL_SYSTEM_CURSOR_WAIT:
   132         nscursor = [NSCursor arrowCursor];
   133         break;
   134     case SDL_SYSTEM_CURSOR_CROSSHAIR:
   135         nscursor = [NSCursor crosshairCursor];
   136         break;
   137     case SDL_SYSTEM_CURSOR_WAITARROW:
   138         nscursor = [NSCursor arrowCursor];
   139         break;
   140     case SDL_SYSTEM_CURSOR_SIZENWSE:
   141     case SDL_SYSTEM_CURSOR_SIZENESW:
   142         nscursor = [NSCursor closedHandCursor];
   143         break;
   144     case SDL_SYSTEM_CURSOR_SIZEWE:
   145         nscursor = [NSCursor resizeLeftRightCursor];
   146         break;
   147     case SDL_SYSTEM_CURSOR_SIZENS:
   148         nscursor = [NSCursor resizeUpDownCursor];
   149         break;
   150     case SDL_SYSTEM_CURSOR_SIZEALL:
   151         nscursor = [NSCursor closedHandCursor];
   152         break;
   153     case SDL_SYSTEM_CURSOR_NO:
   154         nscursor = [NSCursor operationNotAllowedCursor];
   155         break;
   156     case SDL_SYSTEM_CURSOR_HAND:
   157         nscursor = [NSCursor pointingHandCursor];
   158         break;
   159     default:
   160         SDL_assert(!"Unknown system cursor");
   161         return NULL;
   162     }
   163 
   164     if (nscursor) {
   165         cursor = SDL_calloc(1, sizeof(*cursor));
   166         if (cursor) {
   167             /* We'll free it later, so retain it here */
   168             [nscursor retain];
   169             cursor->driverdata = nscursor;
   170         }
   171     }
   172 
   173     [pool release];
   174 
   175     return cursor;
   176 }
   177 
   178 static void
   179 Cocoa_FreeCursor(SDL_Cursor * cursor)
   180 {
   181     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   182     NSCursor *nscursor = (NSCursor *)cursor->driverdata;
   183 
   184     [nscursor release];
   185     SDL_free(cursor);
   186 
   187     [pool release];
   188 }
   189 
   190 static int
   191 Cocoa_ShowCursor(SDL_Cursor * cursor)
   192 {
   193     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   194 
   195     SDL_VideoDevice *device = SDL_GetVideoDevice();
   196     SDL_Window *window = (device ? device->windows : NULL);
   197     for (; window != NULL; window = window->next) {
   198         SDL_WindowData *driverdata = (SDL_WindowData *)window->driverdata;
   199         if (driverdata) {
   200             [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
   201                                                    withObject:[driverdata->nswindow contentView]
   202                                                 waitUntilDone:NO];
   203         }
   204     }
   205 
   206     [pool release];
   207 
   208     return 0;
   209 }
   210 
   211 static void
   212 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
   213 {
   214     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   215     if ([data->listener isMoving])
   216     {
   217         DLog("Postponing warp, window being moved.");
   218         [data->listener setPendingMoveX:x
   219                                       Y:y];
   220         return;
   221     }
   222 
   223     SDL_Mouse *mouse = SDL_GetMouse();
   224     CGPoint point = CGPointMake(x + (float)window->x, y + (float)window->y);
   225 
   226     {
   227         /* This makes Cocoa_HandleMouseEvent ignore this delta in the next
   228          * movement event.
   229          */
   230         SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   231         NSPoint location =  [NSEvent mouseLocation];
   232         driverdata->deltaXOffset = location.x - point.x;
   233         driverdata->deltaYOffset = point.y - location.y;
   234         DLog("Warp to (%g, %g), offsetting next movement event by (%i, %i)", point.x, point.y, driverdata->deltaXOffset, driverdata->deltaYOffset);
   235     }
   236 
   237     /* According to the docs, this was deprecated in 10.6, but it's still
   238      * around. The substitute requires a CGEventSource, but I'm not entirely
   239      * sure how we'd procure the right one for this event.
   240      */
   241     CGSetLocalEventsSuppressionInterval(0.0);
   242     CGWarpMouseCursorPosition(point);
   243     CGSetLocalEventsSuppressionInterval(0.25);
   244 
   245     if (!mouse->relative_mode) {
   246         /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
   247          * other implementations' APIs.
   248          */
   249         SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y);
   250     }
   251 }
   252 
   253 static int
   254 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
   255 {
   256     CGError result;
   257 
   258     if (enabled) {
   259         DLog("Turning on.");
   260         result = CGAssociateMouseAndMouseCursorPosition(NO);
   261     } else {
   262         DLog("Turning off.");
   263         result = CGAssociateMouseAndMouseCursorPosition(YES);
   264     }
   265     if (result != kCGErrorSuccess) {
   266         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   267     }
   268     return 0;
   269 }
   270 
   271 void
   272 Cocoa_InitMouse(_THIS)
   273 {
   274     SDL_Mouse *mouse = SDL_GetMouse();
   275 
   276     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   277 
   278     mouse->CreateCursor = Cocoa_CreateCursor;
   279     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
   280     mouse->ShowCursor = Cocoa_ShowCursor;
   281     mouse->FreeCursor = Cocoa_FreeCursor;
   282     mouse->WarpMouse = Cocoa_WarpMouse;
   283     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   284 
   285     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   286 
   287     Cocoa_InitMouseEventTap(mouse->driverdata);
   288 }
   289 
   290 void
   291 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   292 {
   293     SDL_Mouse *mouse = SDL_GetMouse();
   294 
   295     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   296     if (!mouse->relative_mode) {
   297         return;
   298     }
   299 
   300     switch ([event type])
   301     {
   302         case NSMouseMoved:
   303         case NSLeftMouseDragged:
   304         case NSRightMouseDragged:
   305         case NSOtherMouseDragged:
   306             break;
   307 
   308         default:
   309             /* Ignore any other events. */
   310             return;
   311     }
   312 
   313     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   314     if ([event window]) {
   315         NSRect windowRect = [[[event window] contentView] frame];
   316         if (!NSPointInRect([event locationInWindow], windowRect)) {
   317             return;
   318         }
   319     }
   320 
   321     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   322     float x = [event deltaX] + driverdata->deltaXOffset;
   323     float y = [event deltaY] + driverdata->deltaYOffset;
   324     driverdata->deltaXOffset = driverdata->deltaYOffset = 0;
   325 
   326     if (driverdata->deltaYOffset > 0 || driverdata->deltaXOffset > 0) {
   327         DLog("Relative move was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], x, y);
   328     }
   329 
   330     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)x, (int)y);
   331 }
   332 
   333 void
   334 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   335 {
   336     SDL_Mouse *mouse = SDL_GetMouse();
   337 
   338     float x = -[event deltaX];
   339     float y = [event deltaY];
   340 
   341     if (x > 0) {
   342         x += 0.9f;
   343     } else if (x < 0) {
   344         x -= 0.9f;
   345     }
   346     if (y > 0) {
   347         y += 0.9f;
   348     } else if (y < 0) {
   349         y -= 0.9f;
   350     }
   351     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y);
   352 }
   353 
   354 void
   355 Cocoa_QuitMouse(_THIS)
   356 {
   357     SDL_Mouse *mouse = SDL_GetMouse();
   358     if (mouse) {
   359         if (mouse->driverdata) {
   360             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   361         }
   362 
   363         SDL_free(mouse->driverdata);
   364     }
   365 }
   366 
   367 #endif /* SDL_VIDEO_DRIVER_COCOA */
   368 
   369 /* vi: set ts=4 sw=4 expandtab: */