src/events/SDL_mouse.c
author Jørgen P. Tjernø <jorgen@valvesoftware.com>
Wed, 24 Apr 2013 12:20:51 -0700
changeset 7107 2fcf7bf1d2b2
parent 7104 4e4ca313000c
child 7191 75360622e65f
permissions -rw-r--r--
Move cursor into window when enabling relative mode or gaining focus in relative mode.

This prevents wonky behavior where the clicks won't go to the window
because the cursor was outside it when we enabled relative mode.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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_config.h"
    22 
    23 /* General mouse handling code for SDL */
    24 
    25 #include "SDL_assert.h"
    26 #include "SDL_events.h"
    27 #include "SDL_events_c.h"
    28 #include "default_cursor.h"
    29 #include "../video/SDL_sysvideo.h"
    30 
    31 /*#define DEBUG_MOUSE*/
    32 
    33 /* The mouse state */
    34 static SDL_Mouse SDL_mouse;
    35 
    36 static int
    37 SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y);
    38 
    39 /* Public functions */
    40 int
    41 SDL_MouseInit(void)
    42 {
    43     SDL_Mouse *mouse = SDL_GetMouse();
    44 
    45     mouse->cursor_shown = SDL_TRUE;
    46 
    47     return (0);
    48 }
    49 
    50 void
    51 SDL_SetDefaultCursor(SDL_Cursor * cursor)
    52 {
    53     SDL_Mouse *mouse = SDL_GetMouse();
    54 
    55     mouse->def_cursor = cursor;
    56     if (!mouse->cur_cursor) {
    57         SDL_SetCursor(cursor);
    58     }
    59 }
    60 
    61 SDL_Mouse *
    62 SDL_GetMouse(void)
    63 {
    64     return &SDL_mouse;
    65 }
    66 
    67 SDL_Window *
    68 SDL_GetMouseFocus(void)
    69 {
    70     SDL_Mouse *mouse = SDL_GetMouse();
    71 
    72     return mouse->focus;
    73 }
    74 
    75 void
    76 SDL_ResetMouse(void)
    77 {
    78     SDL_Mouse *mouse = SDL_GetMouse();
    79     Uint8 i;
    80 
    81 #ifdef DEBUG_MOUSE
    82     printf("Resetting mouse\n");
    83 #endif
    84     for (i = 1; i <= sizeof(mouse->buttonstate)*8; ++i) {
    85         if (mouse->buttonstate & SDL_BUTTON(i)) {
    86             SDL_SendMouseButton(mouse->focus, mouse->mouseID, SDL_RELEASED, i);
    87         }
    88     }
    89     SDL_assert(mouse->buttonstate == 0);
    90 }
    91 
    92 void
    93 SDL_SetMouseFocus(SDL_Window * window)
    94 {
    95     SDL_Mouse *mouse = SDL_GetMouse();
    96 
    97     if (mouse->focus == window) {
    98         return;
    99     }
   100 
   101     /* Actually, this ends up being a bad idea, because most operating
   102        systems have an implicit grab when you press the mouse button down
   103        so you can drag things out of the window and then get the mouse up
   104        when it happens.  So, #if 0...
   105     */
   106 #if 0
   107     if (mouse->focus && !window) {
   108         /* We won't get anymore mouse messages, so reset mouse state */
   109         SDL_ResetMouse();
   110     }
   111 #endif
   112 
   113     /* See if the current window has lost focus */
   114     if (mouse->focus) {
   115         SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_LEAVE, 0, 0);
   116     }
   117 
   118     mouse->focus = window;
   119 
   120     if (mouse->focus) {
   121         SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_ENTER, 0, 0);
   122     }
   123 
   124     /* Update cursor visibility */
   125     SDL_SetCursor(NULL);
   126 }
   127 
   128 /* Check to see if we need to synthesize focus events */
   129 static SDL_bool
   130 SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate)
   131 {
   132     SDL_Mouse *mouse = SDL_GetMouse();
   133     int w, h;
   134     SDL_bool inWindow;
   135 
   136     SDL_GetWindowSize(window, &w, &h);
   137     if (x < 0 || y < 0 || x >= w || y >= h) {
   138         inWindow = SDL_FALSE;
   139     } else {
   140         inWindow = SDL_TRUE;
   141     }
   142 
   143 /* Linux doesn't give you mouse events outside your window unless you grab
   144    the pointer.
   145 
   146    Windows doesn't give you mouse events outside your window unless you call
   147    SetCapture().
   148 
   149    Both of these are slightly scary changes, so for now we'll punt and if the
   150    mouse leaves the window you'll lose mouse focus and reset button state.
   151 */
   152 #ifdef SUPPORT_DRAG_OUTSIDE_WINDOW
   153     if (!inWindow && !buttonstate) {
   154 #else
   155     if (!inWindow) {
   156 #endif
   157         if (window == mouse->focus) {
   158 #ifdef DEBUG_MOUSE
   159             printf("Mouse left window, synthesizing move & focus lost event\n");
   160 #endif
   161             SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
   162             SDL_SetMouseFocus(NULL);
   163         }
   164         return SDL_FALSE;
   165     }
   166 
   167     if (window != mouse->focus) {
   168 #ifdef DEBUG_MOUSE
   169          printf("Mouse entered window, synthesizing focus gain & move event\n");
   170 #endif
   171          SDL_SetMouseFocus(window);
   172          SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
   173     }
   174     return SDL_TRUE;
   175 }
   176 
   177 int
   178 SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
   179 {
   180     if (window && !relative) {
   181         SDL_Mouse *mouse = SDL_GetMouse();
   182         if (!SDL_UpdateMouseFocus(window, x, y, mouse->buttonstate)) {
   183             return 0;
   184         }
   185     }
   186 
   187     return SDL_PrivateSendMouseMotion(window, mouseID, relative, x, y);
   188 }
   189 
   190 static int
   191 SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
   192 {
   193     SDL_Mouse *mouse = SDL_GetMouse();
   194     int posted;
   195     int xrel;
   196     int yrel;
   197     int x_max = 0, y_max = 0;
   198 
   199     /* relative motion is calculated regarding the system cursor last position */
   200     if (relative) {
   201         xrel = x;
   202         yrel = y;
   203         x = (mouse->last_x + x);
   204         y = (mouse->last_y + y);
   205     } else {
   206         xrel = x - mouse->last_x;
   207         yrel = y - mouse->last_y;
   208     }
   209 
   210     /* Drop events that don't change state */
   211     if (!xrel && !yrel) {
   212 #ifdef DEBUG_MOUSE
   213         printf("Mouse event didn't change state - dropped!\n");
   214 #endif
   215         return 0;
   216     }
   217 
   218     /* Update internal mouse coordinates */
   219     if (mouse->relative_mode == SDL_FALSE) {
   220         mouse->x = x;
   221         mouse->y = y;
   222     } else {
   223         mouse->x += xrel;
   224         mouse->y += yrel;
   225     }
   226 
   227     SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
   228     --x_max;
   229     --y_max;
   230 
   231     /* make sure that the pointers find themselves inside the windows */
   232     if (mouse->x > x_max) {
   233         mouse->x = x_max;
   234     }
   235     if (mouse->x < 0) {
   236         mouse->x = 0;
   237     }
   238 
   239     if (mouse->y > y_max) {
   240         mouse->y = y_max;
   241     }
   242     if (mouse->y < 0) {
   243         mouse->y = 0;
   244     }
   245 
   246     mouse->xdelta += xrel;
   247     mouse->ydelta += yrel;
   248 
   249 #if 0 /* FIXME */
   250     /* Move the mouse cursor, if needed */
   251     if (mouse->cursor_shown && !mouse->relative_mode &&
   252         mouse->MoveCursor && mouse->cur_cursor) {
   253         mouse->MoveCursor(mouse->cur_cursor);
   254     }
   255 #endif
   256 
   257     /* Post the event, if desired */
   258     posted = 0;
   259     if (SDL_GetEventState(SDL_MOUSEMOTION) == SDL_ENABLE) {
   260         SDL_Event event;
   261         event.motion.type = SDL_MOUSEMOTION;
   262         event.motion.windowID = mouse->focus ? mouse->focus->id : 0;
   263         event.motion.which = mouseID;
   264         event.motion.state = mouse->buttonstate;
   265         event.motion.x = mouse->x;
   266         event.motion.y = mouse->y;
   267         event.motion.xrel = xrel;
   268         event.motion.yrel = yrel;
   269         posted = (SDL_PushEvent(&event) > 0);
   270     }
   271     /* Use unclamped values if we're getting events outside the window */
   272     mouse->last_x = x;
   273     mouse->last_y = y;
   274     return posted;
   275 }
   276 
   277 int
   278 SDL_SendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state, Uint8 button)
   279 {
   280     SDL_Mouse *mouse = SDL_GetMouse();
   281     int posted;
   282     Uint32 type;
   283     Uint32 buttonstate = mouse->buttonstate;
   284 
   285     /* Figure out which event to perform */
   286     switch (state) {
   287     case SDL_PRESSED:
   288         type = SDL_MOUSEBUTTONDOWN;
   289         buttonstate |= SDL_BUTTON(button);
   290         break;
   291     case SDL_RELEASED:
   292         type = SDL_MOUSEBUTTONUP;
   293         buttonstate &= ~SDL_BUTTON(button);
   294         break;
   295     default:
   296         /* Invalid state -- bail */
   297         return 0;
   298     }
   299 
   300     /* We do this after calculating buttonstate so button presses gain focus */
   301     if (window && state == SDL_PRESSED) {
   302         SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate);
   303     }
   304 
   305     if (buttonstate == mouse->buttonstate) {
   306         /* Ignore this event, no state change */
   307         return 0;
   308     }
   309     mouse->buttonstate = buttonstate;
   310 
   311     /* Post the event, if desired */
   312     posted = 0;
   313     if (SDL_GetEventState(type) == SDL_ENABLE) {
   314         SDL_Event event;
   315         event.type = type;
   316         event.button.windowID = mouse->focus ? mouse->focus->id : 0;
   317         event.button.which = mouseID;
   318         event.button.state = state;
   319         event.button.button = button;
   320         event.button.x = mouse->x;
   321         event.button.y = mouse->y;
   322         posted = (SDL_PushEvent(&event) > 0);
   323     }
   324 
   325     /* We do this after dispatching event so button releases can lose focus */
   326     if (window && state == SDL_RELEASED) {
   327         SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate);
   328     }
   329 
   330     return posted;
   331 }
   332 
   333 int
   334 SDL_SendMouseWheel(SDL_Window * window, SDL_MouseID mouseID, int x, int y)
   335 {
   336     SDL_Mouse *mouse = SDL_GetMouse();
   337     int posted;
   338 
   339     if (window) {
   340         SDL_SetMouseFocus(window);
   341     }
   342 
   343     if (!x && !y) {
   344         return 0;
   345     }
   346 
   347     /* Post the event, if desired */
   348     posted = 0;
   349     if (SDL_GetEventState(SDL_MOUSEWHEEL) == SDL_ENABLE) {
   350         SDL_Event event;
   351         event.type = SDL_MOUSEWHEEL;
   352         event.wheel.windowID = mouse->focus ? mouse->focus->id : 0;
   353         event.wheel.which = mouseID;
   354         event.wheel.x = x;
   355         event.wheel.y = y;
   356         posted = (SDL_PushEvent(&event) > 0);
   357     }
   358     return posted;
   359 }
   360 
   361 void
   362 SDL_MouseQuit(void)
   363 {
   364 }
   365 
   366 Uint32
   367 SDL_GetMouseState(int *x, int *y)
   368 {
   369     SDL_Mouse *mouse = SDL_GetMouse();
   370 
   371     if (x) {
   372         *x = mouse->x;
   373     }
   374     if (y) {
   375         *y = mouse->y;
   376     }
   377     return mouse->buttonstate;
   378 }
   379 
   380 Uint32
   381 SDL_GetRelativeMouseState(int *x, int *y)
   382 {
   383     SDL_Mouse *mouse = SDL_GetMouse();
   384 
   385     if (x) {
   386         *x = mouse->xdelta;
   387     }
   388     if (y) {
   389         *y = mouse->ydelta;
   390     }
   391     mouse->xdelta = 0;
   392     mouse->ydelta = 0;
   393     return mouse->buttonstate;
   394 }
   395 
   396 void
   397 SDL_WarpMouseInWindow(SDL_Window * window, int x, int y)
   398 {
   399     SDL_Mouse *mouse = SDL_GetMouse();
   400 	
   401 	if ( window == NULL )
   402 		window = mouse->focus;
   403 	
   404 	if ( window == NULL )
   405 		return;
   406 
   407     if (mouse->WarpMouse) {
   408         mouse->WarpMouse(window, x, y);
   409     } else {
   410         SDL_SendMouseMotion(window, mouse->mouseID, 0, x, y);
   411     }
   412 }
   413 
   414 int
   415 SDL_SetRelativeMouseMode(SDL_bool enabled)
   416 {
   417     SDL_Mouse *mouse = SDL_GetMouse();
   418     SDL_Window *focusWindow = SDL_GetKeyboardFocus();
   419     int original_x = mouse->x, original_y = mouse->y;
   420 
   421     if (enabled == mouse->relative_mode) {
   422         return 0;
   423     }
   424 
   425     if (!mouse->SetRelativeMouseMode) {
   426         return SDL_Unsupported();
   427     }
   428 
   429     if (enabled && focusWindow) {
   430         /* Center it in the focused window to prevent clicks from going through
   431          * to background windows.
   432          */
   433         SDL_SetMouseFocus(focusWindow);
   434         SDL_WarpMouseInWindow(focusWindow, focusWindow->w/2, focusWindow->h/2);
   435     }
   436 
   437     if (mouse->SetRelativeMouseMode(enabled) < 0) {
   438         return -1;
   439     }
   440 
   441     /* Set the relative mode */
   442     mouse->relative_mode = enabled;
   443 
   444     if (enabled) {
   445         /* Save the expected mouse position */
   446         mouse->original_x = original_x;
   447         mouse->original_y = original_y;
   448     } else if (mouse->focus) {
   449         /* Restore the expected mouse position */
   450         SDL_WarpMouseInWindow(mouse->focus, mouse->original_x, mouse->original_y);
   451     }
   452 
   453     /* Flush pending mouse motion */
   454     SDL_FlushEvent(SDL_MOUSEMOTION);
   455 
   456     /* Update cursor visibility */
   457     SDL_SetCursor(NULL);
   458 
   459     return 0;
   460 }
   461 
   462 SDL_bool
   463 SDL_GetRelativeMouseMode()
   464 {
   465     SDL_Mouse *mouse = SDL_GetMouse();
   466 
   467     return mouse->relative_mode;
   468 }
   469 
   470 SDL_Cursor *
   471 SDL_CreateCursor(const Uint8 * data, const Uint8 * mask,
   472                  int w, int h, int hot_x, int hot_y)
   473 {
   474     SDL_Surface *surface;
   475     SDL_Cursor *cursor;
   476     int x, y;
   477     Uint32 *pixel;
   478     Uint8 datab = 0, maskb = 0;
   479     const Uint32 black = 0xFF000000;
   480     const Uint32 white = 0xFFFFFFFF;
   481     const Uint32 transparent = 0x00000000;
   482 
   483     /* Make sure the width is a multiple of 8 */
   484     w = ((w + 7) & ~7);
   485 
   486     /* Create the surface from a bitmap */
   487     surface = SDL_CreateRGBSurface(0, w, h, 32,
   488                                    0x00FF0000,
   489                                    0x0000FF00,
   490                                    0x000000FF,
   491                                    0xFF000000);
   492     if (!surface) {
   493         return NULL;
   494     }
   495     for (y = 0; y < h; ++y) {
   496         pixel = (Uint32 *) ((Uint8 *) surface->pixels + y * surface->pitch);
   497         for (x = 0; x < w; ++x) {
   498             if ((x % 8) == 0) {
   499                 datab = *data++;
   500                 maskb = *mask++;
   501             }
   502             if (maskb & 0x80) {
   503                 *pixel++ = (datab & 0x80) ? black : white;
   504             } else {
   505                 *pixel++ = (datab & 0x80) ? black : transparent;
   506             }
   507             datab <<= 1;
   508             maskb <<= 1;
   509         }
   510     }
   511 
   512     cursor = SDL_CreateColorCursor(surface, hot_x, hot_y);
   513 
   514     SDL_FreeSurface(surface);
   515 
   516     return cursor;
   517 }
   518 
   519 SDL_Cursor *
   520 SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)
   521 {
   522     SDL_Mouse *mouse = SDL_GetMouse();
   523     SDL_Surface *temp = NULL;
   524     SDL_Cursor *cursor;
   525 
   526     if (!surface) {
   527         SDL_SetError("Passed NULL cursor surface");
   528         return NULL;
   529     }
   530 
   531     if (!mouse->CreateCursor) {
   532         SDL_SetError("Cursors are not currently supported");
   533         return NULL;
   534     }
   535 
   536     /* Sanity check the hot spot */
   537     if ((hot_x < 0) || (hot_y < 0) ||
   538         (hot_x >= surface->w) || (hot_y >= surface->h)) {
   539         SDL_SetError("Cursor hot spot doesn't lie within cursor");
   540         return NULL;
   541     }
   542 
   543     if (surface->format->format != SDL_PIXELFORMAT_ARGB8888) {
   544         temp = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0);
   545         if (!temp) {
   546             return NULL;
   547         }
   548         surface = temp;
   549     }
   550 
   551     cursor = mouse->CreateCursor(surface, hot_x, hot_y);
   552     if (cursor) {
   553         cursor->next = mouse->cursors;
   554         mouse->cursors = cursor;
   555     }
   556 
   557     if (temp) {
   558         SDL_FreeSurface(temp);
   559     }
   560 
   561     return cursor;
   562 }
   563 
   564 SDL_Cursor *
   565 SDL_CreateSystemCursor(SDL_SystemCursor id)
   566 {
   567     SDL_Mouse *mouse = SDL_GetMouse();
   568     SDL_Cursor *cursor;
   569 
   570     if (!mouse->CreateSystemCursor) {
   571         SDL_SetError("CreateSystemCursor is not currently supported");
   572         return NULL;
   573     }
   574 
   575 	cursor = mouse->CreateSystemCursor(id);
   576     if (cursor) {
   577         cursor->next = mouse->cursors;
   578         mouse->cursors = cursor;
   579     }
   580 
   581 	return cursor;
   582 }
   583 
   584 /* SDL_SetCursor(NULL) can be used to force the cursor redraw,
   585    if this is desired for any reason.  This is used when setting
   586    the video mode and when the SDL window gains the mouse focus.
   587  */
   588 void
   589 SDL_SetCursor(SDL_Cursor * cursor)
   590 {
   591     SDL_Mouse *mouse = SDL_GetMouse();
   592 
   593     /* Set the new cursor */
   594     if (cursor) {
   595         /* Make sure the cursor is still valid for this mouse */
   596         if (cursor != mouse->def_cursor) {
   597             SDL_Cursor *found;
   598             for (found = mouse->cursors; found; found = found->next) {
   599                 if (found == cursor) {
   600                     break;
   601                 }
   602             }
   603             if (!found) {
   604                 SDL_SetError("Cursor not associated with the current mouse");
   605                 return;
   606             }
   607         }
   608         mouse->cur_cursor = cursor;
   609     } else {
   610         if (mouse->focus) {
   611             cursor = mouse->cur_cursor;
   612         } else {
   613             cursor = mouse->def_cursor;
   614         }
   615     }
   616 
   617     if (cursor && mouse->cursor_shown && !mouse->relative_mode) {
   618         if (mouse->ShowCursor) {
   619             mouse->ShowCursor(cursor);
   620         }
   621     } else {
   622         if (mouse->ShowCursor) {
   623             mouse->ShowCursor(NULL);
   624         }
   625     }
   626 }
   627 
   628 SDL_Cursor *
   629 SDL_GetCursor(void)
   630 {
   631     SDL_Mouse *mouse = SDL_GetMouse();
   632 
   633     if (!mouse) {
   634         return NULL;
   635     }
   636     return mouse->cur_cursor;
   637 }
   638 
   639 SDL_Cursor *
   640 SDL_GetDefaultCursor(void)
   641 {
   642     SDL_Mouse *mouse = SDL_GetMouse();
   643 
   644     if (!mouse) {
   645         return NULL;
   646     }
   647     return mouse->def_cursor;
   648 }
   649 
   650 void
   651 SDL_FreeCursor(SDL_Cursor * cursor)
   652 {
   653     SDL_Mouse *mouse = SDL_GetMouse();
   654     SDL_Cursor *curr, *prev;
   655 
   656     if (!cursor) {
   657         return;
   658     }
   659 
   660     if (cursor == mouse->def_cursor) {
   661         return;
   662     }
   663     if (cursor == mouse->cur_cursor) {
   664         SDL_SetCursor(mouse->def_cursor);
   665     }
   666 
   667     for (prev = NULL, curr = mouse->cursors; curr;
   668          prev = curr, curr = curr->next) {
   669         if (curr == cursor) {
   670             if (prev) {
   671                 prev->next = curr->next;
   672             } else {
   673                 mouse->cursors = curr->next;
   674             }
   675 
   676             if (mouse->FreeCursor) {
   677                 mouse->FreeCursor(curr);
   678             }
   679             return;
   680         }
   681     }
   682 }
   683 
   684 int
   685 SDL_ShowCursor(int toggle)
   686 {
   687     SDL_Mouse *mouse = SDL_GetMouse();
   688     SDL_bool shown;
   689 
   690     if (!mouse) {
   691         return 0;
   692     }
   693 
   694     shown = mouse->cursor_shown;
   695     if (toggle >= 0) {
   696         if (toggle) {
   697             mouse->cursor_shown = SDL_TRUE;
   698         } else {
   699             mouse->cursor_shown = SDL_FALSE;
   700         }
   701         if (mouse->cursor_shown != shown) {
   702             SDL_SetCursor(NULL);
   703         }
   704     }
   705     return shown;
   706 }
   707 
   708 /* vi: set ts=4 sw=4 expandtab: */