Entrées clavier et souris avec la boucle de message WndProc

keyboard

todo : remplacer les scancodes par les virtual codes

Intro :

A l’inverse de l’utilisation du composant DirectInput de DirectX, nous pouvons directement utiliser les réponses de la boucle de message WndProc en tant qu’entrées clavier-souris.

Cet article fournira la classe InputManager utilisée dans d’autres articles.

Explications :

Voici le code pour le fichier InputManager.h

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#include <windows.h>
#include <map>
#include <vector>
#include <sstream>
#include <iostream>

#define BIT(x) 1 << x

#include "Singleton.h"

// Objet d'entrée clavier qui sera envoyé au KeyListener enregistré
// dans la classe InputManager
class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : keyCode(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int keyCode;

    // La caractère correspondant de la touche
    const wchar_t text;
};

// Classe KeyListener qui permet à n'importe quel objet héritant de
// cette classe de recevoir les entrées clavier
class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void OnKeyPressed(const KeyEvent &arg) = 0;
    virtual void OnKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    // Touches spéciales
    enum KeyModifier
    {
        SHIFT = BIT(0),
        CTRL  = BIT(1),
        ALT   = BIT(2)
    };

    // Bouttons de la souris
    enum MouseButton
    {
        MouseButtonLeft,
        MouseButtonRight,
        MouseButtonMiddle,
        MouseButtoNone
    };

    InputManager();
    virtual ~InputManager();

    // Méthodes d'enregistrements des entrées clavier de la boucle
    // de message Win32 WndProc
    void InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyModifierDown(KeyModifier modifier);
    void InjectKeyModifierUp(KeyModifier modifier);

    // Méthodes d'enregistrements des entrées souris de la boucle
    // de message Win32 WndProc
    void InjectMousePosition(int iMouseX, int iMouseY);
    void InjectMouseButtonDown(MouseButton button);
    void InjectMouseButtonUp();

    // Méthodes de récupérations de la position de la souris
    void GetMouseAbsoluteLocation(int& iMouseX, int& iMouseY);
    void GetMouseRelativePosition(float& fX, float& fY);

    // Méthodes de récupérations des entrées clavier
    bool IsKeyDown(unsigned int key);
    bool IsKeyUp(unsigned int key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsKeyModifierDown(KeyModifier modifier);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie le caractère wchar_t correspondant à la clé spécifié
    wchar_t GetKeyCodeAsWChar(char key);

    // Renvoie une chaîne de caractère correspondant à la clé spécifié
    std::string GetKeyCodeAsString(char key);

    // Met le curseur au centre de la fenêtre
    void CenterMouseCursor();

    // Je place cette fonction utilitaire au sein même
    // de la classe, mais sa place serait plus juste ailleurs
    std::string Formater(const std::wstring& src)
    {
        char outString[512];
        CharToOemW(src.c_str(), outString);

        return std::string(outString);
    }

private:
    // Coordonnées de la souris
    int m_iMouseX;
    int m_iMouseY;

    // Variable représentant les combinaison de touches
    // de modification
    unsigned int m_iModifiers;

    // Touches préssées
    std::map<unsigned int, bool> m_pressedKeys;
 
    // L'écouteur d'entrée clavier
    KeyListener* m_pKeyListener;

    // Le boutton de la souris^^
    MouseButton m_iMouseButton;

    // variable ppour gérer les accents comme :
    // û, ì, ò, ô, etc...
    wchar_t m_deadKey;
};

#endif

 

Voici le code pour le fichier InputManager.cpp :

#include "InputManager.h"
#include "System.h"

#include <sstream>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_iMouseX(0),
m_iMouseY(0),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_iMouseButton(MouseButton::MouseButtoNone),
m_deadKey('\0')
{
}

InputManager::~InputManager()
{
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsKeyModifierDown(KeyModifier modifier)
{
    return m_iModifiers & modifier;
}

void InputManager::InjectKeyModifierDown(KeyModifier modifier)
{
    m_iModifiers |= modifier;
}

void InputManager::InjectKeyModifierUp(KeyModifier modifier)
{
    m_iModifiers &= ~(modifier);
}

void InputManager::InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = true;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyPressed(keyEvent);

    }
}

void InputManager::InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = false;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyReleased(keyEvent);
    }
}

void InputManager::InjectMousePosition(int iMouseX, int iMouseY)
{
    m_iMouseX = iMouseX;
    m_iMouseY = iMouseY;
}

void InputManager::GetMouseAbsoluteLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

// todo : essayer avec les m_iMouseX et m_iMouseY
void InputManager::GetMouseRelativePosition(float& fX, float& fY)
{
    POINT p;
    if (!GetCursorPos(&p))
    {
        DWORD xy = GetMessagePos();
        p.x = LOWORD(xy);
        p.y = HIWORD(xy);
    }

    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    GetWindowRect(SYSTEM->GetHwnd(), &rect);

    float x = p.x - rect.left - borderX;
    float y = p.y - rect.top - borderY;

    fX = ((float) x / (float) SYSTEM->GetWindowWidth()) - 0.5f;
    fY = ((float) y / (float) SYSTEM->GetWindowHeight()) - 0.5f;
}

bool InputManager::IsKeyDown(unsigned int key)
{
    return m_pressedKeys[key] == true;
}

bool InputManager::IsKeyUp(unsigned int key)
{
    return m_pressedKeys[key] == false;
}

void InputManager::InjectMouseButtonDown(MouseButton button)
{
    m_iMouseButton = button;
}

void InputManager::InjectMouseButtonUp()
{
    m_iMouseButton = MouseButtoNone;
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return m_iMouseButton == button;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    wchar_t w = GetKeyCodeAsWChar(key);

    std::wstring wStr;
    wStr.push_back( w );

    std::string str = Formater(wStr);

    return str;
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);

    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if (ascii == 1 && m_deadKey != '\0')
    {
        WCHAR wcBuff[3] = {buff[0], m_deadKey, '\0'};
        WCHAR out[3];

        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == -1)
    {    
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadKey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadKey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadKey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadKey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadKey = 0x327;
                break;
            default:
                m_deadKey = buff[0];
                break;
        }

    }    
    else if (ascii == 1)
    {        
        m_deadKey = '\0';
        return buff[0];
    }

    return 0;
}

void InputManager::CenterMouseCursor()
{
    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    HWND hwnd = SYSTEM->GetHwnd();

    GetWindowRect(hwnd, &rect);

    unsigned int iWindowWidth = SYSTEM->GetWindowWidth();
    unsigned int iWindowHeight = SYSTEM->GetWindowHeight();

    SetCursorPos(0.5f * iWindowWidth + rect.left + borderX, 0.5f * iWindowHeight + rect.top + borderY);
}

 

Voici la fonction WndProc utilisée pour obtenir les entrées afin de les passer à la classe InputManager :

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (!InputManager::IsCreated())
    {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_KEYDOWN:    
            {
                INPUT_MANAGER->InjectKeyDown(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::ALT);
                }
            }
            break;

        case WM_KEYUP:
            {
                INPUT_MANAGER->InjectKeyUp(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::ALT);
                }

                break;
            }
        case WM_MOUSEMOVE:
            {
                int iMouseX = (int) LOWORD(lParam);
                int iMouseY = (int) HIWORD(lParam);

                INPUT_MANAGER->InjectMousePosition(iMouseX, iMouseY);

                break;
            }
        case WM_LBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonLeft);

                break;
            }
        case WM_RBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonRight);

                break;
            }
        case WM_MBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonMiddle);

                break;
            }
        case WM_MBUTTONUP:
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
            {
                INPUT_MANAGER->InjectMouseButtonUp();

                break;
            }
        default:
            {            
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
    }
 
    return 0;
}

 

Résumé :

Nous avons présenter une manière de récupérer les entrées clavier de votre jeu ou application.

Voici l’archive du code complet pour cet RawInputManager.zip

Références :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *