src/video/winrt/SDL_winrtmouse.cpp
author David Ludwig <dludwig@pobox.com>
Thu, 29 Aug 2013 10:32:16 -0400
changeset 8513 0bcf3970deba
parent 8512 5b345a756965
child 8514 8ba600edd93f
permissions -rw-r--r--
WinRT: added touch-event support for Windows Phone devices

Support for touch events in Windows 8/RT is pending on further R+D.
     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 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   161     /* Init touch: */
   162     SDL_AddTouch(WINRT_TouchID, "");
   163 #endif
   164 }
   165 
   166 void
   167 WINRT_QuitMouse(_THIS)
   168 {
   169 }
   170 
   171 // Applies necessary geometric transformations to raw cursor positions:
   172 Windows::Foundation::Point
   173 WINRT_TransformCursorPosition(SDL_Window * window, Windows::Foundation::Point rawPosition)
   174 {
   175     using namespace Windows::Graphics::Display;
   176 
   177     if (!window) {
   178         return rawPosition;
   179     }
   180 
   181     SDL_WindowData * windowData = (SDL_WindowData *) window->driverdata;
   182     if (windowData->coreWindow == nullptr) {
   183         // For some reason, the window isn't associated with a CoreWindow.
   184         // This might end up being the case as XAML support is extended.
   185         // For now, if there's no CoreWindow attached to the SDL_Window,
   186         // don't do any transforms.
   187         return rawPosition;
   188     }
   189 
   190     // The CoreWindow can only be accessed on certain thread(s).
   191     SDL_assert(CoreWindow::GetForCurrentThread() != nullptr);
   192 
   193     CoreWindow ^ nativeWindow = windowData->coreWindow.Get();
   194     Windows::Foundation::Point outputPosition;
   195 
   196 #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
   197     outputPosition.X = rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   198     outputPosition.Y = rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   199 #else
   200     switch (DisplayProperties::CurrentOrientation)
   201     {
   202         case DisplayOrientations::Portrait:
   203             outputPosition.X = rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   204             outputPosition.Y = rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   205             break;
   206         case DisplayOrientations::PortraitFlipped:
   207             outputPosition.X = (float32)window->w - rawPosition.X * (((float32)window->w) / nativeWindow->Bounds.Width);
   208             outputPosition.Y = (float32)window->h - rawPosition.Y * (((float32)window->h) / nativeWindow->Bounds.Height);
   209             break;
   210         case DisplayOrientations::Landscape:
   211             outputPosition.X = rawPosition.Y * (((float32)window->w) / nativeWindow->Bounds.Height);
   212             outputPosition.Y = (float32)window->h - rawPosition.X * (((float32)window->h) / nativeWindow->Bounds.Width);
   213             break;
   214         case DisplayOrientations::LandscapeFlipped:
   215             outputPosition.X = (float32)window->w - rawPosition.Y * (((float32)window->w) / nativeWindow->Bounds.Height);
   216             outputPosition.Y = rawPosition.X * (((float32)window->h) / nativeWindow->Bounds.Width);
   217             break;
   218         default:
   219             break;
   220     }
   221 #endif
   222 
   223     return outputPosition;
   224 }
   225 
   226 static inline int
   227 _lround(float arg)
   228 {
   229     if (arg >= 0.0f) {
   230         return (int)floor(arg + 0.5f);
   231     } else {
   232         return (int)ceil(arg - 0.5f);
   233     }
   234 }
   235 
   236 void
   237 WINRT_ProcessMouseMovedEvent(SDL_Window * window, Windows::Devices::Input::MouseEventArgs ^args)
   238 {
   239     if (!window || !WINRT_UseRelativeMouseMode) {
   240         return;
   241     }
   242 
   243     // DLudwig, 2012-12-28: On some systems, namely Visual Studio's Windows
   244     // Simulator, as well as Windows 8 in a Parallels 8 VM, MouseEventArgs'
   245     // MouseDelta field often reports very large values.  More information
   246     // on this can be found at the following pages on MSDN:
   247     //  - http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/a3c789fa-f1c5-49c4-9c0a-7db88d0f90f8
   248     //  - https://connect.microsoft.com/VisualStudio/Feedback/details/756515
   249     //
   250     // The values do not appear to be as large when running on some systems,
   251     // most notably a Surface RT.  Furthermore, the values returned by
   252     // CoreWindow's PointerMoved event, and sent to this class' OnPointerMoved
   253     // method, do not ever appear to be large, even when MouseEventArgs'
   254     // MouseDelta is reporting to the contrary.
   255     //
   256     // On systems with the large-values behavior, it appears that the values
   257     // get reported as if the screen's size is 65536 units in both the X and Y
   258     // dimensions.  This can be viewed by using Windows' now-private, "Raw Input"
   259     // APIs.  (GetRawInputData, RegisterRawInputDevices, WM_INPUT, etc.)
   260     //
   261     // MSDN's documentation on MouseEventArgs' MouseDelta field (at
   262     // http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.input.mouseeventargs.mousedelta ),
   263     // does not seem to indicate (to me) that its values should be so large.  It
   264     // says that its values should be a "change in screen location".  I could
   265     // be misinterpreting this, however a post on MSDN from a Microsoft engineer (see: 
   266     // http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/09a9868e-95bb-4858-ba1a-cb4d2c298d62 ),
   267     // indicates that these values are in DIPs, which is the same unit used
   268     // by CoreWindow's PointerMoved events (via the Position field in its CurrentPoint
   269     // property.  See http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.input.pointerpoint.position.aspx
   270     // for details.)
   271     //
   272     // To note, PointerMoved events are sent a 'RawPosition' value (via the
   273     // CurrentPoint property in MouseEventArgs), however these do not seem
   274     // to exhibit the same large-value behavior.
   275     //
   276     // The values passed via PointerMoved events can't always be used for relative
   277     // mouse motion, unfortunately.  Its values are bound to the cursor's position,
   278     // which stops when it hits one of the screen's edges.  This can be a problem in
   279     // first person shooters, whereby it is normal for mouse motion to travel far
   280     // along any one axis for a period of time.  MouseMoved events do not have the
   281     // screen-bounding limitation, and can be used regardless of where the system's
   282     // cursor is.
   283     //
   284     // One possible workaround would be to programmatically set the cursor's
   285     // position to the screen's center (when SDL's relative mouse mode is enabled),
   286     // however WinRT does not yet seem to have the ability to set the cursor's
   287     // position via a public API.  Win32 did this via an API call, SetCursorPos,
   288     // however WinRT makes this function be private.  Apps that use it won't get
   289     // approved for distribution in the Windows Store.  I've yet to be able to find
   290     // a suitable, store-friendly counterpart for WinRT.
   291     //
   292     // There may be some room for a workaround whereby OnPointerMoved's values
   293     // are compared to the values from OnMouseMoved in order to detect
   294     // when this bug is active.  A suitable transformation could then be made to
   295     // OnMouseMoved's values.  For now, however, the system-reported values are sent
   296     // to SDL with minimal transformation: from native screen coordinates (in DIPs)
   297     // to SDL window coordinates.
   298     //
   299     const Windows::Foundation::Point mouseDeltaInDIPs((float)args->MouseDelta.X, (float)args->MouseDelta.Y);
   300     const Windows::Foundation::Point mouseDeltaInSDLWindowCoords = WINRT_TransformCursorPosition(window, mouseDeltaInDIPs);
   301     SDL_SendMouseMotion(
   302         window,
   303         0,
   304         1,
   305         _lround(mouseDeltaInSDLWindowCoords.X),
   306         _lround(mouseDeltaInSDLWindowCoords.Y));
   307 }
   308 
   309 Uint8
   310 WINRT_GetSDLButtonForPointerPoint(Windows::UI::Input::PointerPoint ^pt)
   311 {
   312     using namespace Windows::UI::Input;
   313 
   314 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   315     return SDL_BUTTON_LEFT;
   316 #else
   317     switch (pt->Properties->PointerUpdateKind)
   318     {
   319         case PointerUpdateKind::LeftButtonPressed:
   320         case PointerUpdateKind::LeftButtonReleased:
   321             return SDL_BUTTON_LEFT;
   322 
   323         case PointerUpdateKind::RightButtonPressed:
   324         case PointerUpdateKind::RightButtonReleased:
   325             return SDL_BUTTON_RIGHT;
   326 
   327         case PointerUpdateKind::MiddleButtonPressed:
   328         case PointerUpdateKind::MiddleButtonReleased:
   329             return SDL_BUTTON_MIDDLE;
   330 
   331         case PointerUpdateKind::XButton1Pressed:
   332         case PointerUpdateKind::XButton1Released:
   333             return SDL_BUTTON_X1;
   334 
   335         case PointerUpdateKind::XButton2Pressed:
   336         case PointerUpdateKind::XButton2Released:
   337             return SDL_BUTTON_X2;
   338 
   339         default:
   340             break;
   341     }
   342 #endif
   343 
   344     return 0;
   345 }
   346 
   347 //const char *
   348 //WINRT_ConvertPointerUpdateKindToString(Windows::UI::Input::PointerUpdateKind kind)
   349 //{
   350 //    using namespace Windows::UI::Input;
   351 //
   352 //    switch (kind)
   353 //    {
   354 //        case PointerUpdateKind::Other:
   355 //            return "Other";
   356 //        case PointerUpdateKind::LeftButtonPressed:
   357 //            return "LeftButtonPressed";
   358 //        case PointerUpdateKind::LeftButtonReleased:
   359 //            return "LeftButtonReleased";
   360 //        case PointerUpdateKind::RightButtonPressed:
   361 //            return "RightButtonPressed";
   362 //        case PointerUpdateKind::RightButtonReleased:
   363 //            return "RightButtonReleased";
   364 //        case PointerUpdateKind::MiddleButtonPressed:
   365 //            return "MiddleButtonPressed";
   366 //        case PointerUpdateKind::MiddleButtonReleased:
   367 //            return "MiddleButtonReleased";
   368 //        case PointerUpdateKind::XButton1Pressed:
   369 //            return "XButton1Pressed";
   370 //        case PointerUpdateKind::XButton1Released:
   371 //            return "XButton1Released";
   372 //        case PointerUpdateKind::XButton2Pressed:
   373 //            return "XButton2Pressed";
   374 //        case PointerUpdateKind::XButton2Released:
   375 //            return "XButton2Released";
   376 //    }
   377 //
   378 //    return "";
   379 //}
   380 
   381 void
   382 WINRT_ProcessPointerMovedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   383 {
   384     if (!window || WINRT_UseRelativeMouseMode) {
   385         return;
   386     }
   387 
   388     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   389 
   390     if (pointerPoint->PointerId == WINRT_LeftFingerDown) {
   391         SDL_SendMouseMotion(window, 0, 0, (int)transformedPoint.X, (int)transformedPoint.Y);
   392     }
   393 
   394 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   395     // TODO, WinRT: make touch input work with Windows 8/RT, seeing if touches can be distinguished from mouse input.
   396     SDL_SendTouchMotion(
   397         WINRT_TouchID,
   398         (SDL_FingerID) pointerPoint->PointerId,
   399         transformedPoint.X,
   400         transformedPoint.Y,
   401         pointerPoint->Properties->Pressure);
   402 #endif
   403 }
   404 
   405 void
   406 WINRT_ProcessPointerWheelChangedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   407 {
   408     if (!window) {
   409         return;
   410     }
   411 
   412     // FIXME: This may need to accumulate deltas up to WHEEL_DELTA
   413     short motion = pointerPoint->Properties->MouseWheelDelta / WHEEL_DELTA;
   414     SDL_SendMouseWheel(window, 0, 0, motion);
   415 }
   416 
   417 void WINRT_ProcessPointerReleasedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   418 {
   419     if (!window) {
   420         return;
   421     }
   422 
   423     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   424 
   425     if (WINRT_LeftFingerDown == pointerPoint->PointerId) {
   426         Uint8 button = WINRT_GetSDLButtonForPointerPoint(pointerPoint);
   427         if (button) {
   428             SDL_SendMouseButton(window, 0, SDL_RELEASED, button);
   429         }
   430         WINRT_LeftFingerDown = 0;
   431     }
   432 
   433 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   434     SDL_SendTouch(
   435         WINRT_TouchID,
   436         (SDL_FingerID) pointerPoint->PointerId,
   437         SDL_FALSE,
   438         transformedPoint.X,
   439         transformedPoint.Y,
   440         pointerPoint->Properties->Pressure);
   441 #endif
   442 }
   443 
   444 void WINRT_ProcessPointerPressedEvent(SDL_Window *window, Windows::UI::Input::PointerPoint ^pointerPoint)
   445 {
   446     if (!window) {
   447         return;
   448     }
   449 
   450     Windows::Foundation::Point transformedPoint = WINRT_TransformCursorPosition(window, pointerPoint->Position);
   451 
   452     if (!WINRT_LeftFingerDown) {
   453         Uint8 button = WINRT_GetSDLButtonForPointerPoint(pointerPoint);
   454         if (button) {
   455 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   456             SDL_SendMouseMotion(window, 0, 0, (int)transformedPoint.X, (int)transformedPoint.Y);
   457 #endif
   458             SDL_SendMouseButton(window, 0, SDL_PRESSED, button);
   459         }
   460 
   461         WINRT_LeftFingerDown = pointerPoint->PointerId;
   462     }
   463 
   464 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
   465     SDL_SendTouch(
   466         WINRT_TouchID,
   467         (SDL_FingerID) pointerPoint->PointerId,
   468         SDL_TRUE,
   469         transformedPoint.X,
   470         transformedPoint.Y,
   471         pointerPoint->Properties->Pressure);
   472 #endif
   473 }
   474 
   475 #endif /* SDL_VIDEO_DRIVER_WINRT */
   476 
   477 /* vi: set ts=4 sw=4 expandtab: */