CroCo
CroCo

Reputation: 5741

How to implement consistent approach for detecting mouse button being hold

I'm trying to implement Mouse class to handle any related issues to mouse actions. Everything works fine except detecting a mouse button being hold. The library doesn't provide this functionality for the mouse yet there is an option for keyboard event (i.e. through REPEAT flag). I have to implement it manually. The first and simple approach is to set a flag for press and release button

bool Mouse::isRightDown()
{
    if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_PRESS){
        m_isRightHold = true;
        ...
}

bool Mouse::isRightUp()
{
    if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_RELEASE ){
        m_isRightHold = false;
        ...
}

bool Mouse::isRightHold()
{
    if ( g_RightFlag ){
        ...
        return true;
    }
    return false;
}

Now in rendering loop, I can do the following

while(!glfwWindowShouldClose(window)){
    glfwPollEvents();
    ...
    // Handle Right Button Mouse
    if ( Mouse::Instance()->isRightHold() ){
        std::cout << "Right Mouse Button is hold..." << std::endl;
    }
    ...
    glfwSwapBuffers(window);
}

But the problem with this approach is the fact that while-loop is faster than human's reaction for releasing the button, therefore, single click will be considered as a hold event. I've considered another approach by updating a global Boolean variable (i.e. g_RightFlag). The variable will be updated every 900 second in an independent thread as follows

while (true){

        //std::this_thread::sleep_for(delay);


        std::chrono::duration<int, std::ratio<1, 1000>> delay(900);

        bool sleep = true;
        auto start = std::chrono::system_clock::now();
        while(sleep)
        {
            auto end = std::chrono::system_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
            if ( elapsed.count() > delay.count() ){
                sleep = false;
            }
        }   

        mtx.lock();
        g_RightFlag = m_isRightHold;
        g_LefFlag   = m_isLeftHold;
        mtx.unlock();
    }   

This solution is better than the first approach but still inconsistent because threads are not synchronized. At some moments, when I do just a single click, the hold event is detected (i.e. in milliseconds). How can I improve my approach to handle Hold Mouse Event?

main.cpp

#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "mouse.h"

int main(void)
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);


    GLFWwindow* window = glfwCreateWindow(800,600,"LearnOpenGL",nullptr,nullptr);
    if( window == nullptr ){
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glewExperimental = GL_TRUE;
    if( glewInit() != GLEW_OK ){
        std::cout << "Failed to initialize GLEW" << std::endl;
        return -1;
    }

    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0,0, width, height);

    // callback events
    //Keyboard Event;
    Mouse::Instance();
    Mouse::Instance()->init(window);

    while(!glfwWindowShouldClose(window)){
        glfwPollEvents();

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Handle Right Button Mouse
        if ( Mouse::Instance()->isRightDown() ){
            std::cout << "Right Mouse Button is pressed..." << std::endl;
        }

        if ( Mouse::Instance()->isRightUp() ){
            std::cout << "Right Mouse Button is released..." << std::endl;
        }

        if ( Mouse::Instance()->isRightHold() ){
            std::cout << "Right Mouse Button is hold..." << std::endl;
        }

        // Handle Left Button Mouse
        if ( Mouse::Instance()->isLeftDown() ){
            std::cout << "Left Mouse Button is pressed..." << std::endl;
        }

        if ( Mouse::Instance()->isLeftUp() ){
            std::cout << "Left Mouse Button is released..." << std::endl;
        }

        if ( Mouse::Instance()->isLeftHold() ){
            std::cout << "Left Mouse Button is hold..." << std::endl;
        }

        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

mouse.h

#ifndef MOUSE_H
#define MOUSE_H

#include <thread>
#include <atomic>
#include <chrono>
#include <GLFW/glfw3.h>

class Mouse
{
public:

    static Mouse* Instance(){
        if(s_pInstance == NULL)
            s_pInstance = new Mouse;
        return s_pInstance;
    }
    void init(GLFWwindow* window);
    bool isRightDown();
    bool isRightUp();
    bool isRightHold();

    bool isLeftDown();
    bool isLeftUp();
    bool isLeftHold();

    std::atomic<int> m_button, m_action, m_mode;
private:
    static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);


    bool m_isRightHold, m_isLeftHold;

    GLFWwindow* m_pWindow;
    Mouse();
    static Mouse* s_pInstance;
    std::thread m_OnHoldThread;
    void initThread();
    void updateThread();
    void update(int b, int a, int m);

};

#endif

mouse.cpp

#include "mouse.h"
#include <iostream>
#include <mutex> // std::mutex

std::mutex mtx;

Mouse* Mouse::s_pInstance = NULL;
bool g_LefFlag(false);
bool g_RightFlag(false);

Mouse::Mouse()
    : m_button(-1), m_action(-1), m_mode(-1),
      m_isRightHold(false), m_isLeftHold(false)
{
    initThread();
}

void Mouse::init(GLFWwindow* window)
{
    m_pWindow = window;
    glfwSetMouseButtonCallback(window, mouse_button_callback);
}

void Mouse::mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
    Mouse::Instance()->update(button, action, mods);
}

void Mouse::initThread()
{
    m_OnHoldThread = std::thread(&Mouse::updateThread,this);
}


void Mouse::updateThread()
{
    //std::chrono::milliseconds delay(1100);

    while (true){

        //std::this_thread::sleep_for(delay);


        std::chrono::duration<int, std::ratio<1, 1000>> delay(900);

        bool sleep = true;
        auto start = std::chrono::system_clock::now();
        while(sleep)
        {
            auto end = std::chrono::system_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
            if ( elapsed.count() > delay.count() ){
                sleep = false;
            }
        }   

        mtx.lock();
        g_RightFlag = m_isRightHold;
        g_LefFlag   = m_isLeftHold;
        mtx.unlock();
    }   
}


bool Mouse::isRightDown()
{
    if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_PRESS){

        m_isRightHold = true;
        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;
        return true;
    }

    return false;
}

bool Mouse::isRightUp()
{
    if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_RELEASE ){
        m_isRightHold = false;

        mtx.lock();
        g_RightFlag = m_isRightHold;
        mtx.unlock();

        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;
        return true;
    }

    return false;
}

bool Mouse::isRightHold()
{
    if ( g_RightFlag ){
        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;

        return true;
    }

    return false;
}


bool Mouse::isLeftDown()
{
    if (m_button == GLFW_MOUSE_BUTTON_LEFT && m_action == GLFW_PRESS){

        m_isLeftHold = true;
        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;
        return true;
    }

    return false;
}
bool Mouse::isLeftUp()
{
    if (m_button == GLFW_MOUSE_BUTTON_LEFT && m_action == GLFW_RELEASE ){
        m_isLeftHold = false;

        mtx.lock();
        g_LefFlag = m_isLeftHold;
        mtx.unlock();

        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;
        return true;
    }

    return false;
}
bool Mouse::isLeftHold()
{
    if ( g_LefFlag ){
        m_button   = -1;
        m_action   = -1;
        m_mode     = -1;

        return true;
    }

    return false;
}

void Mouse::update(int b, int a, int m)
{
    m_button   = b;
    m_action   = a;
    m_mode     = m;
}

Upvotes: 0

Views: 675

Answers (1)

user7860670
user7860670

Reputation: 37587

Why don't you just get a high-resolution time of m_isRightHold = true; event and compare time period elapsed since then at every main loop iteration while m_isRightHold continues to be true to determine that mouse button has been held for long enough to consider a click or hold to happen?

Upvotes: 1

Related Questions