Skip to content

File ButtonMonitor.h

File List > external-docs > libDaisy > src > ui > ButtonMonitor.h

Go to the documentation of this file

Source Code

#pragma once
#include <stdint.h>
#include "UiEventQueue.h"
#include "../sys/system.h"

namespace daisy
{
template <typename BackendType, uint32_t numButtons>
class ButtonMonitor
{
  public:
    ButtonMonitor()
    : queue_(nullptr),
      backend_(nullptr),
      timeout_(0),
      doubleClickTimeout_(0),
      retriggerTimeoutMs_(0),
      retriggerPeriodMs_(0)
    {
    }

    void Init(UiEventQueue& queueToAddEventsTo,
              BackendType&  backend,
              uint16_t      debounceTimeoutMs    = 50,
              uint32_t      doubleClickTimeoutMs = 500,
              uint32_t      retriggerTimeoutMs   = 2000,
              uint32_t      retriggerPeriodMs    = 50)
    {
        queue_              = &queueToAddEventsTo;
        backend_            = &backend;
        timeout_            = debounceTimeoutMs;
        doubleClickTimeout_ = doubleClickTimeoutMs;
        retriggerTimeoutMs_ = retriggerTimeoutMs;
        retriggerPeriodMs_  = retriggerPeriodMs;

        for(uint32_t i = 0; i < numButtons; i++)
        {
            buttonStates_[i]        = -timeout_; // starting in "released" state
            lastClickTimes_[i]      = 0;
            lastRetriggerTimes_[i]  = 0;
            numSuccessiveClicks_[i] = 0;
        }

        lastCallSysTime_ = System::GetNow();
    }

    void Process()
    {
        const auto now      = System::GetNow();
        const auto timeDiff = now - lastCallSysTime_;
        lastCallSysTime_    = now;

        for(uint32_t i = 0; i < numButtons; i++)
            ProcessButton(i, backend_->IsButtonPressed(i), timeDiff, now);
    }

    bool IsButtonPressed(uint16_t buttonId) const
    {
        if(buttonId >= numButtons)
            return false;
        else
            return buttonStates_[buttonId] >= timeout_;
    }

    BackendType& GetBackend() { return backend_; }

    uint16_t GetNumButtonsMonitored() const { return numButtons; }

  private:
    void ProcessButton(uint16_t id,
                       bool     isPressed,
                       uint32_t timeInMsSinceLastCall,
                       uint32_t currentSystemTime)
    {
        // released or transitioning there...
        if(buttonStates_[id] < 0)
        {
            if(!isPressed)
            {
                // transitioning?
                if(buttonStates_[id] + 1 > -timeout_)
                {
                    buttonStates_[id] -= timeInMsSinceLastCall;
                    if(buttonStates_[id] + 1 <= -timeout_)
                        queue_->AddButtonReleased(id);
                }
            }
            // start transitioning towards "pressed"
            else
            {
                buttonStates_[id] = 1;
                // timeout could be set to "0" - no debouncing, send immediately.
                if(buttonStates_[id] - 1 >= timeout_)
                    PostPhysicalButtonDownEvent(id, currentSystemTime);
            }
        }
        else
        {
            if(isPressed)
            {
                // transitioning?
                if(buttonStates_[id] - 1 < timeout_)
                {
                    buttonStates_[id] += timeInMsSinceLastCall;
                    if(buttonStates_[id] - 1 >= timeout_)
                        PostPhysicalButtonDownEvent(id, currentSystemTime);
                }
                // already pressed - check retriggering if enabled
                else if(retriggerTimeoutMs_ > 0)
                {
                    const auto timeSincePress
                        = currentSystemTime - lastClickTimes_[id];
                    if(timeSincePress >= retriggerTimeoutMs_)
                    {
                        const auto timeSinceLastRetrigger
                            = currentSystemTime - lastRetriggerTimes_[id];
                        if(timeSinceLastRetrigger > retriggerPeriodMs_)
                        {
                            lastRetriggerTimes_[id] = currentSystemTime;
                            queue_->AddButtonPressed(
                                id, numSuccessiveClicks_[id], true);
                        }
                    }
                }
            }
            // start transitioning towards "released"
            else
            {
                buttonStates_[id] = -1;
                // timeout could be set to "0" - no debouncing, send immediately.
                if(buttonStates_[id] + 1 <= -timeout_)
                    queue_->AddButtonReleased(id);
            }
        }
    }

    void PostPhysicalButtonDownEvent(uint16_t id, uint32_t currentSystemTime)
    {
        const auto timeDiff = currentSystemTime - lastClickTimes_[id];
        if(timeDiff <= doubleClickTimeout_)
            numSuccessiveClicks_[id]++;
        else
            numSuccessiveClicks_[id] = 1;

        lastClickTimes_[id] = currentSystemTime;
        queue_->AddButtonPressed(id, numSuccessiveClicks_[id], false);
    }

    ButtonMonitor(const ButtonMonitor&) = delete;
    ButtonMonitor& operator=(const ButtonMonitor&) = delete;

    UiEventQueue* queue_;
    BackendType*  backend_;
    uint16_t      timeout_;
    uint32_t      doubleClickTimeout_;
    uint32_t      retriggerTimeoutMs_;
    uint32_t      retriggerPeriodMs_;
    int16_t       buttonStates_[numButtons]; // <= -timeout --> not pressed,
                                             // >= timeout_ --> pressed
    uint32_t lastClickTimes_[numButtons];
    uint32_t lastRetriggerTimes_[numButtons];
    uint8_t  numSuccessiveClicks_[numButtons];
    uint32_t lastCallSysTime_;
};

} // namespace daisy