src/video/winrt/SDL_winrtmouse.cpp
author David Ludwig <dludwig@pobox.com>
Wed, 28 Aug 2013 16:51:07 -0400
changeset 8512 5b345a756965
parent 8511 326b43e1276b
child 8513 0bcf3970deba
permissions -rw-r--r--
WinRT: corrected SDL_MOUSE* coordinates in non-Portrait modes

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