src/video/winrt/SDL_winrtmouse.cpp
author David Ludwig <dludwig@pobox.com>
Sun, 01 Sep 2013 10:20:17 -0400
changeset 8514 8ba600edd93f
parent 8513 0bcf3970deba
child 8515 bc6cf9201dab
permissions -rw-r--r--
WinRT: added touch input event support for Windows 8/RT devices
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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 
    22 #include "SDL_config.h"
    23 
    24 #if SDL_VIDEO_DRIVER_WINRT
    25 
    26 /*
    27  * Windows includes:
    28  */
    29 #include <Windows.h>
    30 using namespace Windows::UI::Core;
    31 using Windows::UI::Core::CoreCursor;
    32 
    33 /*
    34  * SDL includes:
    35  */
    36 extern "C" {
    37 #include "SDL_assert.h"
    38 #include "../../events/SDL_mouse_c.h"
    39 #include "../../events/SDL_touch_c.h"
    40 #include "../SDL_sysvideo.h"
    41 #include "SDL_events.h"
    42 #include "SDL_log.h"
    43 }
    44 
    45 #include "../../core/winrt/SDL_winrtapp.h"
    46 #include "SDL_winrtvideo_cpp.h"
    47 #include "SDL_winrtmouse.h"
    48 
    49 
    50 static SDL_bool WINRT_UseRelativeMouseMode = SDL_FALSE;
    51 static SDL_TouchID WINRT_TouchID = 1;
    52 static unsigned int WINRT_LeftFingerDown = 0;
    53 
    54 
    55 static SDL_Cursor *
    56 WINRT_CreateSystemCursor(SDL_SystemCursor id)
    57 {
    58     SDL_Cursor *cursor;
    59     CoreCursorType cursorType = CoreCursorType::Arrow;
    60 
    61     switch(id)
    62     {
    63     default:
    64         SDL_assert(0);
    65         return NULL;
    66     case SDL_SYSTEM_CURSOR_ARROW:     cursorType = CoreCursorType::Arrow; break;
    67     case SDL_SYSTEM_CURSOR_IBEAM:     cursorType = CoreCursorType::IBeam; break;
    68     case SDL_SYSTEM_CURSOR_WAIT:      cursorType = CoreCursorType::Wait; break;
    69     case SDL_SYSTEM_CURSOR_CROSSHAIR: cursorType = CoreCursorType::Cross; break;
    70     case SDL_SYSTEM_CURSOR_WAITARROW: cursorType = CoreCursorType::Wait; break;
    71     case SDL_SYSTEM_CURSOR_SIZENWSE:  cursorType = CoreCursorType::SizeNorthwestSoutheast; break;
    72     case SDL_SYSTEM_CURSOR_SIZENESW:  cursorType = CoreCursorType::SizeNortheastSouthwest; break;
    73     case SDL_SYSTEM_CURSOR_SIZEWE:    cursorType = CoreCursorType::SizeWestEast; break;
    74     case SDL_SYSTEM_CURSOR_SIZENS:    cursorType = CoreCursorType::SizeNorthSouth; break;
    75     case SDL_SYSTEM_CURSOR_SIZEALL:   cursorType = CoreCursorType::SizeAll; break;
    76     case SDL_SYSTEM_CURSOR_NO:        cursorType = CoreCursorType::UniversalNo; break;
    77     case SDL_SYSTEM_CURSOR_HAND:      cursorType = CoreCursorType::Hand; break;
    78     }
    79 
    80     cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor));
    81     if (cursor) {
    82         /* Create a pointer to a COM reference to a cursor.  The extra
    83            pointer is used (on top of the COM reference) to allow the cursor
    84            to be referenced by the SDL_cursor's driverdata field, which is
    85            a void pointer.
    86         */
    87         CoreCursor ^* theCursor = new CoreCursor^(nullptr);
    88         *theCursor = ref new CoreCursor(cursorType, 0);
    89         cursor->driverdata = (void *) theCursor;
    90     } else {
    91         SDL_OutOfMemory();
    92     }
    93 
    94     return cursor;
    95 }
    96 
    97 static SDL_Cursor *
    98 WINRT_CreateDefaultCursor()
    99 {
   100     return WINRT_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
   101 }
   102 
   103 static void
   104 WINRT_FreeCursor(SDL_Cursor * cursor)
   105 {
   106     if (cursor->driverdata) {
   107         CoreCursor ^* theCursor = (CoreCursor ^*) cursor->driverdata;
   108         *theCursor = nullptr;       // Release the COM reference to the CoreCursor
   109         delete theCursor;           // Delete the pointer to the COM reference
   110     }
   111     SDL_free(cursor);
   112 }
   113 
   114 static int
   115 WINRT_ShowCursor(SDL_Cursor * cursor)
   116 {
   117     // TODO, WinRT, XAML: make WINRT_ShowCursor work when XAML support is enabled.
   118     if ( ! CoreWindow::GetForCurrentThread()) {
   119         return 0;
   120     }
   121 
   122     if (cursor) {
   123         CoreCursor ^* theCursor = (CoreCursor ^*) cursor->driverdata;
   124         CoreWindow::GetForCurrentThread()->PointerCursor = *theCursor;
   125     } else {
   126         CoreWindow::GetForCurrentThread()->PointerCursor = nullptr;
   127     }
   128     return 0;
   129 }
   130 
   131 static int
   132 WINRT_SetRelativeMouseMode(SDL_bool enabled)
   133 {
   134     WINRT_UseRelativeMouseMode = enabled;
   135     return 0;
   136 }
   137 
   138 void
   139 WINRT_InitMouse(_THIS)
   140 {
   141     SDL_Mouse *mouse = SDL_GetMouse();
   142 
   143     /* DLudwig, Dec 3, 2012: WinRT does not currently provide APIs for
   144        the following features, AFAIK:
   145         - custom cursors  (multiple system cursors are, however, available)
   146         - programmatically moveable cursors
   147     */
   148 
   149 #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
   150     //mouse->CreateCursor = WINRT_CreateCursor;
   151     mouse->CreateSystemCursor = WINRT_CreateSystemCursor;
   152     mouse->ShowCursor = WINRT_ShowCursor;
   153     mouse->FreeCursor = WINRT_FreeCursor;
   154     //mouse->WarpMouse = WINRT_WarpMouse;
   155     mouse->SetRelativeMouseMode = WINRT_SetRelativeMouseMode;
   156 
   157     SDL_SetDefaultCursor(WINRT_CreateDefaultCursor());
   158 #endif
   159 
   160     /* Init touch: */
   161     SDL_AddTouch(WINRT_TouchID, "");
   162 }
   163 
   164 void
   165 WINRT_QuitMouse(_THIS)
   166 {
   167 }
   168 
   169 // Applies necessary geometric transformations to raw cursor positions:
   170 Windows::Foundation::Point
   171 WINRT_TransformCursorPosition(SDL_Window * window, Windows::Foundation::Point rawPosition)
   172 {
   173     using namespace Windows::Graphics::Display;
   174 
   175     if (!window) {
   176         return rawPosition;
   177     }
   178 
   179     SDL_WindowData * windowData = (SDL_WindowData *) window->driverdata;
   180     if (windowData->coreWindow == nullptr) {
   181         // For some reason, the window isn't associated with a CoreWindow.
   182         // This might end up being the case as XAML support is extended.
   183         // For now, if there's no CoreWindow attached to the SDL_Window,
   184         // don't do any transforms.
   185         return rawPosition;
   186     }
   187 
   188     // The CoreWindow can only be accessed on certain thread(s).
   189     SDL_assert(CoreWindow::GetForCurrentThread() != nullptr);
   190 
   191     CoreWindow ^ nativeWindow = windowData->coreWindow.Get();
   192     Windows::Foundation::Point outputPosition;
   193 
   194 #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
   195     outputPosition.X = rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   196     outputPosition.Y = rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   197 #else
   198     switch (DisplayProperties::CurrentOrientation)
   199     {
   200         case DisplayOrientations::Portrait:
   201             outputPosition.X = rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   202             outputPosition.Y = rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   203             break;
   204         case DisplayOrientations::PortraitFlipped:
   205             outputPosition.X = (float32)window->w - rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   206             outputPosition.Y = (float32)window->h - rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   207             break;
   208         case DisplayOrientations::Landscape:
   209             outputPosition.X = rawPosition.Y * (((float32)window->w) / nativeWindow->Bounds.Height);
   210             outputPosition.Y = (float32)window->h - rawPosition.X * (((float32)window->h) / nativeWindow->Bounds.Width);
   211             break;
   212         case DisplayOrientations::LandscapeFlipped:
   213             outputPosition.X = (float32)window->w - rawPosition.Y * (((float32)window->w) / nativeWindow->Bounds.Height);
   214             outputPosition.Y = rawPosition.X * (((float32)window->h) / nativeWindow->Bounds.Width);
   215             break;
   216         default:
   217             break;
   218     }
   219 #endif
   220 
   221     return outputPosition;
   222 }
   223 
   224 static inline int
   225 _lround(float arg)
   226 {
   227     if (arg >= 0.0f) {
   228         return (int)floor(arg + 0.5f);
   229     } else {
   230         return (int)ceil(arg - 0.5f);
   231     }
   232 }
   233 
   234 void
   235 WINRT_ProcessMouseMovedEvent(SDL_Window * window, Windows::Devices::Input::MouseEventArgs ^args)
   236 {
   237     if (!window || !WINRT_UseRelativeMouseMode) {
   238         return;
   239     }
   240 
   241     // DLudwig, 2012-12-28: On some systems, namely Visual Studio's Windows
   242     // Simulator, as well as Windows 8 in a Parallels 8 VM, MouseEventArgs'
   243     // MouseDelta field often reports very large values.  More information
   244     // on this can be found at the following pages on MSDN:
   245     //  - http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/a3c789fa-f1c5-49c4-9c0a-7db88d0f90f8
   246     //  - https://connect.microsoft.com/VisualStudio/Feedback/details/756515
   247     //
   248     // The values do not appear to be as large when running on some systems,
   249     // most notably a Surface RT.  Furthermore, the values returned by
   250     // CoreWindow's PointerMoved event, and sent to this class' OnPointerMoved
   251     // method, do not ever appear to be large, even when MouseEventArgs'
   252     // MouseDelta is reporting to the contrary.
   253     //
   254     // On systems with the large-values behavior, it appears that the values
   255     // get reported as if the screen's size is 65536 units in both the X and Y
   256     // dimensions.  This can be viewed by using Windows' now-private, "Raw Input"
   257     // APIs.  (GetRawInputData, RegisterRawInputDevices, WM_INPUT, etc.)
   258     //
   259     // MSDN's documentation on MouseEventArgs' MouseDelta field (at
   260     // http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.input.mouseeventargs.mousedelta ),
   261     // does not seem to indicate (to me) that its values should be so large.  It
   262     // says that its values should be a "change in screen location".  I could
   263     // be misinterpreting this, however a post on MSDN from a Microsoft engineer (see: 
   264     // http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/09a9868e-95bb-4858-ba1a-cb4d2c298d62 ),
   265     // indicates that these values are in DIPs, which is the same unit used
   266     // by CoreWindow's PointerMoved events (via the Position field in its CurrentPoint
   267     // property.  See http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.input.pointerpoint.position.aspx
   268     // for details.)
   269     //
   270     // To note, PointerMoved events are sent a 'RawPosition' value (via the
   271     // CurrentPoint property in MouseEventArgs), however these do not seem
   272     // to exhibit the same large-value behavior.
   273     //
   274     // The values passed via PointerMoved events can't always be used for relative
   275     // mouse motion, unfortunately.  Its values are bound to the cursor's position,
   276     // which stops when it hits one of the screen's edges.  This can be a problem in
   277     // first person shooters, whereby it is normal for mouse motion to travel far
   278     // along any one axis for a period of time.  MouseMoved events do not have the
   279     // screen-bounding limitation, and can be used regardless of where the system's
   280     // cursor is.
   281     //
   282     // One possible workaround would be to programmatically set the cursor's
   283     // position to the screen's center (when SDL's relative mouse mode is enabled),
   284     // however WinRT does not yet seem to have the ability to set the cursor's
   285     // position via a public API.  Win32 did this via an API call, SetCursorPos,
   286     // however WinRT makes this function be private.  Apps that use it won't get
   287     // approved for distribution in the Windows Store.  I've yet to be able to find
   288     // a suitable, store-friendly counterpart for WinRT.
   289     //
   290     // There may be some room for a workaround whereby OnPointerMoved's values
   291     // are compared to the values from OnMouseMoved in order to detect
   292     // when this bug is active.  A suitable transformation could then be made to
   293     // OnMouseMoved's values.  For now, however, the system-reported values are sent
   294     // to SDL with minimal transformation: from native screen coordinates (in DIPs)
   295     // to SDL window coordinates.
   296     //
   297     const Windows::Foundation::Point mouseDeltaInDIPs((float)args->MouseDelta.X, (float)args->MouseDelta.Y);
   298     const Windows::Foundation::Point mouseDeltaInSDLWindowCoords = WINRT_TransformCursorPosition(window, mouseDeltaInDIPs);
   299     SDL_SendMouseMotion(
   300         window,
   301         0,
   302         1,
   303         _lround(mouseDeltaInSDLWindowCoords.X),
   304         _lround(mouseDeltaInSDLWindowCoords.Y));
   305 }
   306 
   307 Uint8
   308 WINRT_GetSDLButtonForPointerPoint(Windows::UI::Input::PointerPoint ^pt)
   309 {
   310     using namespace Windows::UI::Input;
   311 
   312 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   313     return SDL_BUTTON_LEFT;
   314 #else
   315     switch (pt->Properties->PointerUpdateKind)
   316     {
   317         case PointerUpdateKind::LeftButtonPressed:
   318         case PointerUpdateKind::LeftButtonReleased:
   319             return SDL_BUTTON_LEFT;
   320 
   321         case PointerUpdateKind::RightButtonPressed:
   322         case PointerUpdateKind::RightButtonReleased:
   323             return SDL_BUTTON_RIGHT;
   324 
   325         case PointerUpdateKind::MiddleButtonPressed:
   326         case PointerUpdateKind::MiddleButtonReleased:
   327             return SDL_BUTTON_MIDDLE;
   328 
   329         case PointerUpdateKind::XButton1Pressed:
   330         case PointerUpdateKind::XButton1Released:
   331             return SDL_BUTTON_X1;
   332 
   333         case PointerUpdateKind::XButton2Pressed:
   334         case PointerUpdateKind::XButton2Released:
   335             return SDL_BUTTON_X2;
   336 
   337         default:
   338             break;
   339     }
   340 #endif
   341 
   342     return 0;
   343 }
   344 
   345 //const char *
   346 //WINRT_ConvertPointerUpdateKindToString(Windows::UI::Input::PointerUpdateKind kind)
   347 //{
   348 //    using namespace Windows::UI::Input;
   349 //
   350 //    switch (kind)
   351 //    {
   352 //        case PointerUpdateKind::Other:
   353 //            return "Other";
   354 //        case PointerUpdateKind::LeftButtonPressed:
   355 //            return "LeftButtonPressed";
   356 //        case PointerUpdateKind::LeftButtonReleased:
   357 //            return "LeftButtonReleased";
   358 //        case PointerUpdateKind::RightButtonPressed:
   359 //            return "RightButtonPressed";
   360 //        case PointerUpdateKind::RightButtonReleased:
   361 //            return "RightButtonReleased";
   362 //        case PointerUpdateKind::MiddleButtonPressed:
   363 //            return "MiddleButtonPressed";
   364 //        case PointerUpdateKind::MiddleButtonReleased:
   365 //            return "MiddleButtonReleased";
   366 //        case PointerUpdateKind::XButton1Pressed:
   367 //            return "XButton1Pressed";
   368 //        case PointerUpdateKind::XButton1Released:
   369 //            return "XButton1Released";
   370 //        case PointerUpdateKind::XButton2Pressed:
   371 //            return "XButton2Pressed";
   372 //        case PointerUpdateKind::XButton2Released:
   373 //            return "XButton2Released";
   374 //    }
   375 //
   376 //    return "";
   377 //}
   378 
   379 static bool
   380 WINRT_IsTouchEvent(Windows::UI::Input::PointerPoint ^pointerPoint)
   381 {
   382 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   383     return true;
   384 #else
   385     using namespace Windows::Devices::Input;
   386     switch (pointerPoint->PointerDevice->PointerDeviceType) {
   387         case PointerDeviceType::Touch:
   388         case PointerDeviceType::Pen:
   389             return true;
   390         default:
   391             return false;
   392     }
   393 #endif
   394 }
   395 
   396 void
   397 WINRT_ProcessPointerMovedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   398 {
   399     if (!window || WINRT_UseRelativeMouseMode) {
   400         return;
   401     }
   402 
   403     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   404 
   405     if (pointerPoint->PointerId == WINRT_LeftFingerDown) {
   406         SDL_SendMouseMotion(window, 0, 0, (int)transformedPoint.X, (int)transformedPoint.Y);
   407     }
   408 
   409     if (WINRT_IsTouchEvent(pointerPoint)) {
   410         SDL_SendTouchMotion(
   411             WINRT_TouchID,
   412             (SDL_FingerID) pointerPoint->PointerId,
   413             transformedPoint.X,
   414             transformedPoint.Y,
   415             pointerPoint->Properties->Pressure);
   416     }
   417 }
   418 
   419 void
   420 WINRT_ProcessPointerWheelChangedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   421 {
   422     if (!window) {
   423         return;
   424     }
   425 
   426     // FIXME: This may need to accumulate deltas up to WHEEL_DELTA
   427     short motion = pointerPoint->Properties->MouseWheelDelta / WHEEL_DELTA;
   428     SDL_SendMouseWheel(window, 0, 0, motion);
   429 }
   430 
   431 void WINRT_ProcessPointerReleasedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   432 {
   433     if (!window) {
   434         return;
   435     }
   436 
   437     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   438 
   439     if (WINRT_LeftFingerDown == pointerPoint->PointerId) {
   440         Uint8 button = WINRT_GetSDLButtonForPointerPoint(pointerPoint);
   441         if (button) {
   442             SDL_SendMouseButton(window, 0, SDL_RELEASED, button);
   443         }
   444         WINRT_LeftFingerDown = 0;
   445     }
   446 
   447     if (WINRT_IsTouchEvent(pointerPoint)) {
   448         SDL_SendTouch(
   449             WINRT_TouchID,
   450             (SDL_FingerID) pointerPoint->PointerId,
   451             SDL_FALSE,
   452             transformedPoint.X,
   453             transformedPoint.Y,
   454             pointerPoint->Properties->Pressure);
   455     }
   456 }
   457 
   458 void WINRT_ProcessPointerPressedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   459 {
   460     if (!window) {
   461         return;
   462     }
   463 
   464     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   465 
   466     if (!WINRT_LeftFingerDown) {
   467         Uint8 button = WINRT_GetSDLButtonForPointerPoint(pointerPoint);
   468         if (button) {
   469 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   470             SDL_SendMouseMotion(window, 0, 0, (int)transformedPoint.X, (int)transformedPoint.Y);
   471 #endif
   472             SDL_SendMouseButton(window, 0, SDL_PRESSED, button);
   473         }
   474 
   475         WINRT_LeftFingerDown = pointerPoint->PointerId;
   476     }
   477 
   478     if (WINRT_IsTouchEvent(pointerPoint)) {
   479         SDL_SendTouch(
   480             WINRT_TouchID,
   481             (SDL_FingerID) pointerPoint->PointerId,
   482             SDL_TRUE,
   483             transformedPoint.X,
   484             transformedPoint.Y,
   485             pointerPoint->Properties->Pressure);
   486     }
   487 }
   488 
   489 #endif /* SDL_VIDEO_DRIVER_WINRT */
   490 
   491 /* vi: set ts=4 sw=4 expandtab: */