mathertel / OneButton

An Arduino library for using a single button for multiple purpose input.
http://www.mathertel.de/Arduino/OneButtonLibrary.aspx
Other
920 stars 230 forks source link

Add support for lambda functions. #112

Closed reifiedsteve closed 1 year ago

reifiedsteve commented 1 year ago

In particular, being able to use this useful little class with callbacks that are lambdas-with-context would make this much more useful for use with object member functions. For example:

class MyClass
{
public:

    MyClass()  : _button(7) {
        _button.attachClick([this]() { this->_myFunc(); });
    }

private:

    void _myFunc() {
        // ... do stuff when single clicked.
    }

    OneButton _button;
};

Something along the lines of what is already there, but just extrapolating it...

#include <functional>
...

class OneButton
{
public:
    ...
    typedef std::function<void()> callbackLambda;   // <== ADDED.
    ...
    callbackLambda _clickLambda;   // <== ADDED.
    ...
    void attachClick(callbackLambda newLambda) {    // <== ADDED new method.
        _clickLambda = newLambda;       
    }    

    ...
    void OneButton::Tick(bool activeLevel) 
    {
        ...
        case OneButton::OCS_COUNT:
            ...
            if (_nClicks == 1) {
                // this was 1 click only.
                if (_clickFunc) _clickFunc();
                if (_paramClickFunc) _paramClickFunc(_clickFuncParam);
                if (_clickLambda) _clickLambda();         // <== ADDED.

etc..

gregistech commented 1 year ago

EDIT: see my next comment for A solution.

Yes, this would be useful for my usage too.

#define LEFT_PIN 4
#define RIGHT_PIN 5
#define OK_PIN 6
class BtnHandler {
        OneButton leftBtn = OneButton(LEFT_PIN, true, true);
        OneButton rightBtn = OneButton(RIGHT_PIN, true, true);
        OneButton okBtn = OneButton(OK_PIN, true, true);

        State *state;
        public:
                BtnHandler(State *state_p) {
                        state = state_p;
                }

        OneButton *btns[3] = { &leftBtn, &rightBtn, &okBtn };

        void tickBtns() {
                for (unsigned int i = 0; i < sizeof(btns)/sizeof(btns[0]); i++) {
                        btns[i] -> tick();
                }
        }
        void setupBtns() {
                leftBtn.attachClick([](){addToTarget(-1);});
                rightBtn.attachClick([](){addToTarget(1);});
                okBtn.attachClick([](){
                        switch (*state) {
                                case SETUP: {
                                        if (cur_mult == (long) 1000 * 60 * 60) {
                                                *state = RUNNING;
                                        } else {
                                                cur_mult *= 60ul;
                                        }
                                        break;
                                }
                                case RUNNING: {
                                        *state = IDLE;
                                        break;
                                }
                                case IDLE: {
                                        *state = RUNNING;
                                        break;
                                }
                                case ALARM: {
                                        reset();
                                        break;
                                }
                        }
                });
        }
};

This sadly breaks with error: no matching function for call to 'OneButton::attachClick(void (BtnHandler::*)())'.

Though there could be a solution to this without changing the library as: note: candidate: void OneButton::attachClick(parameterizedCallbackFunction, void*) void attachClick(parameterizedCallbackFunction newFunction, void *parameter);. This means that we could use a workaround I've seen mentioned, but I'm not sure how to apply it for example to my particular use-case.

gregistech commented 1 year ago

EDIT: PR to docs in #114.

Okay, I made it work:

#define LEFT_PIN 4
#define RIGHT_PIN 5
#define OK_PIN 6
class BtnHandler {
        OneButton leftBtn = OneButton(LEFT_PIN, true, true);
        OneButton rightBtn = OneButton(RIGHT_PIN, true, true);
        OneButton okBtn = OneButton(OK_PIN, true, true);

        State *state;
        public:
                BtnHandler(State *state_p) {
                        state = state_p;
                }

        OneButton *btns[3] = { &leftBtn, &rightBtn, &okBtn };

        void tickBtns() {
                for (unsigned int i = 0; i < sizeof(btns)/sizeof(btns[0]); i++) {
                        btns[i] -> tick();
                }
        }
        void setupBtns() {
                leftBtn.attachClick([](){addToTarget(-1);});
                rightBtn.attachClick([](){addToTarget(1);});
                okBtn.attachClick([](void *ctx){
                        switch (*(((BtnHandler*)(ctx)) -> state)) {
                                case SETUP: {
                                        if (cur_mult == (long) 1000 * 60 * 60) {
                                                *(((BtnHandler*)(ctx)) -> state) = RUNNING;
                                        } else {
                                                cur_mult *= 60ul;
                                        }
                                        break;
                                }
                                case RUNNING: {
                                        *(((BtnHandler*)(ctx)) -> state) = IDLE;
                                        break;
                                }
                                case IDLE: {
                                        *(((BtnHandler*)(ctx)) -> state) = RUNNING;
                                        break;
                                }
                                case ALARM: {
                                        reset();
                                        break;
                                }
                        }
                }, this);
        }
};

Basically, we pass the context (so, this or rather, the object we're in) to the library, and it will give it back to the lambda. Thus the capturing issue was worked around. It's kinda ugly, but I think the issue can even be closed: if it can't be made prettier.

mathertel commented 1 year ago

The existing solution was added to the docu. Thanks