text: ﻿//--------------------------------------------------------------------------------------
  // File: Keyboard.cpp
  //
  // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  // PARTICULAR PURPOSE.
  //
  // Copyright (c) Microsoft Corporation. All rights reserved.
  //
  // http://go.microsoft.com/fwlink/?LinkId=248929
  //--------------------------------------------------------------------------------------
  
  #include "pch.h"
  #include "Keyboard.h"
  
  #include "PlatformHelpers.h"
  
  using namespace DirectX;
  using Microsoft::WRL::ComPtr;
  
  static_assert(sizeof(Keyboard::State) == (256 / 8), "Size mismatch for State");
  
  namespace
  {
      void KeyDown(int key, Keyboard::State& state)
      {
          if (key < 0 || key > 0xfe)
              return;
  
          auto ptr = reinterpret_cast<uint32_t*>(&state);
  
          unsigned int bf = 1u << (key & 0x1f);
          ptr[(key >> 5)] |= bf;
      }
  
      void KeyUp(int key, Keyboard::State& state)
      {
          if (key < 0 || key > 0xfe)
              return;
  
          auto ptr = reinterpret_cast<uint32_t*>(&state);
  
          unsigned int bf = 1u << (key & 0x1f);
          ptr[(key >> 5)] &= ~bf;
      }
  }
  
  
  #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
  
  //======================================================================================
  // Windows Store or Universal Windows Platform (UWP) app implementation
  //======================================================================================
  
  //
  // For a Windows Store app or Universal Windows Platform (UWP) app, add the following:
  //
  // void App::SetWindow(CoreWindow^ window )
  // {
  //     m_keyboard->SetWindow(window);
  // }
  //
  
  class Keyboard::Impl
  {
  public:
      Impl(Keyboard* owner) :
          mOwner(owner)
      {
          mAcceleratorKeyToken.value = 0;
          mActivatedToken.value = 0;
  
          if ( s_keyboard )
          {
              throw std::exception( "Keyboard is a singleton" );
          }
  
          s_keyboard = this;
  
          memset( &mState, 0, sizeof(State) );
      }
  
      ~Impl()
      {
          s_keyboard = nullptr;
  
          RemoveHandlers();
      }
  
      void GetState(State& state) const
      {
          memcpy( &state, &mState, sizeof(State) );
      }
  
      void Reset()
      {
          memset( &mState, 0, sizeof(State) );
      }
  
      void SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
      {
          using namespace Microsoft::WRL;
          using namespace Microsoft::WRL::Wrappers;
          using namespace ABI::Windows::UI::Core;
  
          if (mWindow.Get() == window)
              return;
  
          RemoveHandlers();
  
          mWindow = window;
  
          if (!window)
              return;
  
          typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CWindowActivatedEventArgs ActivatedHandler;
          HRESULT hr = window->add_Activated(Callback<ActivatedHandler>(Activated).Get(), &mActivatedToken);
          ThrowIfFailed(hr);
  
          ComPtr<ICoreDispatcher> dispatcher;
          hr = window->get_Dispatcher( dispatcher.GetAddressOf() );
          ThrowIfFailed(hr);
  
          ComPtr<ICoreAcceleratorKeys> keys;
          hr = dispatcher.As(&keys);
          ThrowIfFailed(hr);
  
          typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreDispatcher_Windows__CUI__CCore__CAcceleratorKeyEventArgs AcceleratorKeyHandler;
          hr = keys->add_AcceleratorKeyActivated( Callback<AcceleratorKeyHandler>(AcceleratorKeyEvent).Get(), &mAcceleratorKeyToken);
          ThrowIfFailed(hr);
      }
  
      State       mState;
      Keyboard*   mOwner;
  
      static Keyboard::Impl* s_keyboard;
  
  private:
      ComPtr<ABI::Windows::UI::Core::ICoreWindow> mWindow;
  
      EventRegistrationToken mAcceleratorKeyToken;
      EventRegistrationToken mActivatedToken;
  
      void RemoveHandlers()
      {
          if (mWindow)
          {
              using namespace ABI::Windows::UI::Core;
  
              ComPtr<ICoreDispatcher> dispatcher;
              HRESULT hr = mWindow->get_Dispatcher( dispatcher.GetAddressOf() );
              ThrowIfFailed(hr);
  
              mWindow->remove_Activated(mActivatedToken);
              mActivatedToken.value = 0;
  
              ComPtr<ICoreAcceleratorKeys> keys;
              hr = dispatcher.As(&keys);
              ThrowIfFailed(hr);
  
              keys->remove_AcceleratorKeyActivated(mAcceleratorKeyToken);
              mAcceleratorKeyToken.value = 0;
          }
      }
  
      static HRESULT Activated( IInspectable *, ABI::Windows::UI::Core::IWindowActivatedEventArgs* )
      {
          auto pImpl = Impl::s_keyboard;
  
          if (!pImpl)
              return S_OK;
  
          pImpl->Reset();
  
          return S_OK;
      }
  
      static HRESULT AcceleratorKeyEvent( IInspectable *, ABI::Windows::UI::Core::IAcceleratorKeyEventArgs* args )
      {
          using namespace ABI::Windows::System;
          using namespace ABI::Windows::UI::Core;
  
          auto pImpl = Impl::s_keyboard;
  
          if (!pImpl)
              return S_OK;
  
          CoreAcceleratorKeyEventType evtType;
          HRESULT hr = args->get_EventType(&evtType);
          ThrowIfFailed(hr);
  
          bool down = false;
  
          switch (evtType)
          {
          case CoreAcceleratorKeyEventType_KeyDown:
          case CoreAcceleratorKeyEventType_SystemKeyDown:
              down = true;
              break;
  
          case CoreAcceleratorKeyEventType_KeyUp:
          case CoreAcceleratorKeyEventType_SystemKeyUp:
              break;
  
          default:
              return S_OK;
          }
  
          CorePhysicalKeyStatus status;
          hr = args->get_KeyStatus(&status);
          ThrowIfFailed(hr);
  
          VirtualKey virtualKey;
          hr = args->get_VirtualKey(&virtualKey);
          ThrowIfFailed(hr);
  
          int vk = static_cast<int>( virtualKey );
  
          switch (vk)
          {
          case VK_SHIFT:
              vk = (status.ScanCode == 0x36) ? VK_RSHIFT : VK_LSHIFT;
              if ( !down )
              {
                  // Workaround to ensure left vs. right shift get cleared when both were pressed at same time
                  KeyUp(VK_LSHIFT, pImpl->mState);
                  KeyUp(VK_RSHIFT, pImpl->mState);
              }
              break;
  
          case VK_CONTROL:
              vk = (status.IsExtendedKey) ? VK_RCONTROL : VK_LCONTROL;
              break;
  
          case VK_MENU:
              vk = (status.IsExtendedKey) ? VK_RMENU : VK_LMENU;
              break;
          }
  
          if (down)
          {
              KeyDown(vk, pImpl->mState);
          }
          else
          {
              KeyUp(vk, pImpl->mState);
          }
  
          return S_OK;
      }
  };
  
  
  Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
  
  
  void Keyboard::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
  {
      pImpl->SetWindow(window);
  }
  
  #elif defined(_XBOX_ONE) || ( defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) )
  
  //======================================================================================
  // Null device for Windows Phone and Xbox One
  //======================================================================================
  
  class Keyboard::Impl
  {
  public:
      Impl(Keyboard* owner) :
          mOwner(owner)
      {
          if ( s_keyboard )
          {
              throw std::exception( "Keyboard is a singleton" );
          }
  
          s_keyboard = this;
      }
  
      ~Impl()
      {
          s_keyboard = nullptr;
      }
  
      void GetState(State& state) const
      {
          memset( &state, 0, sizeof(State) );
      }
  
      void Reset()
      {
      }
  
      Keyboard*   mOwner;
  
      static Keyboard::Impl* s_keyboard;
  };
  
  Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
  
  #else
  
  //======================================================================================
  // Win32 desktop implementation
  //======================================================================================
  
  //
  // For a Win32 desktop application, call this function from your Window Message Procedure
  //
  // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  // {
  //     switch (message)
  //     {
  //
  //     case WM_ACTIVATEAPP:
  //         Keyboard::ProcessMessage(message, wParam, lParam);
  //         break;
  //
  //     case WM_KEYDOWN:
  //     case WM_SYSKEYDOWN:
  //     case WM_KEYUP:
  //     case WM_SYSKEYUP:
  //         Keyboard::ProcessMessage(message, wParam, lParam);
  //         break;
  //
  //     }
  // }
  //
  
  class Keyboard::Impl
  {
  public:
      Impl(Keyboard* owner) :
          mOwner(owner)
      {
          if ( s_keyboard )
          {
              throw std::exception( "Keyboard is a singleton" );
          }
  
          s_keyboard = this;
  
          memset( &mState, 0, sizeof(State) );
      }
  
      ~Impl()
      {
          s_keyboard = nullptr;
      }
  
      void GetState(State& state) const
      {
          memcpy( &state, &mState, sizeof(State) );
      }
  
      void Reset()
      {
          memset( &mState, 0, sizeof(State) );
      }
  
      State           mState;
      Keyboard*       mOwner;
  
      static Keyboard::Impl* s_keyboard;
  };
  
  
  Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr;
  
  
  void Keyboard::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
  {
      auto pImpl = Impl::s_keyboard;
  
      if (!pImpl)
          return;
  
      bool down = false;
  
      switch (message)
      {
      case WM_ACTIVATEAPP:
          pImpl->Reset();
          return;
  
      case WM_KEYDOWN:
      case WM_SYSKEYDOWN:
          down = true;
          break;
  
      case WM_KEYUP:
      case WM_SYSKEYUP:
          break;
  
      default:
          return;
      }
  
      int vk = static_cast<int>( wParam );
      switch (vk)
      {
      case VK_SHIFT:
          vk = MapVirtualKey((lParam & 0x00ff0000) >> 16, MAPVK_VSC_TO_VK_EX);
          if ( !down )
          {
              // Workaround to ensure left vs. right shift get cleared when both were pressed at same time
              KeyUp(VK_LSHIFT, pImpl->mState);
              KeyUp(VK_RSHIFT, pImpl->mState);
          }
          break;
  
      case VK_CONTROL:
          vk = (lParam & 0x01000000) ? VK_RCONTROL : VK_LCONTROL;
          break;
  
      case VK_MENU:
          vk = (lParam & 0x01000000) ? VK_RMENU : VK_LMENU;
          break;
      }
  
      if (down)
      {
          KeyDown(vk, pImpl->mState);
      }
      else
      {
          KeyUp(vk, pImpl->mState);
      }
  }
  
  #endif
  
  #pragma warning( disable : 4355 )
  
  // Public constructor.
  Keyboard::Keyboard()
      : pImpl( new Impl(this) )
  {
  }
  
  
  // Move constructor.
  Keyboard::Keyboard(Keyboard&& moveFrom)
    : pImpl(std::move(moveFrom.pImpl))
  {
      pImpl->mOwner = this;
  }
  
  
  // Move assignment.
  Keyboard& Keyboard::operator= (Keyboard&& moveFrom)
  {
      pImpl = std::move(moveFrom.pImpl);
      pImpl->mOwner = this;
      return *this;
  }
  
  
  // Public destructor.
  Keyboard::~Keyboard()
  {
  }
  
  
  Keyboard::State Keyboard::GetState() const
  {
      State state;
      pImpl->GetState(state);
      return state;
  }
  
  
  void Keyboard::Reset()
  {
      pImpl->Reset();
  }
  
  
  Keyboard& Keyboard::Get()
  {
      if ( !Impl::s_keyboard || !Impl::s_keyboard->mOwner )
          throw std::exception( "Keyboard is a singleton" );
  
      return *Impl::s_keyboard->mOwner;
  }
  
  
  
  //======================================================================================
  // KeyboardStateTracker
  //======================================================================================
  
  void Keyboard::KeyboardStateTracker::Update( const State& state )
  {
      auto currPtr = reinterpret_cast<const uint32_t*>(&state);
      auto prevPtr = reinterpret_cast<const uint32_t*>(&lastState);
      auto releasedPtr = reinterpret_cast<uint32_t*>(&released);
      auto pressedPtr = reinterpret_cast<uint32_t*>(&pressed);
      for (size_t j = 0; j < (256 / 32); ++j)
      {
          *pressedPtr = *currPtr & ~(*prevPtr);
          *releasedPtr = ~(*currPtr) & *prevPtr;
  
          ++currPtr;
          ++prevPtr;
          ++releasedPtr;
          ++pressedPtr;
      }
  
      lastState = state;
  }
  
  
  void Keyboard::KeyboardStateTracker::Reset()
  {
      memset( this, 0, sizeof(KeyboardStateTracker) );
  }
  