Closures used as callbacks

57 views
Skip to first unread message

Gonzalo Garramuno

unread,
Oct 17, 2022, 7:15:07 AM10/17/22
to fltkg...@googlegroups.com
My guess is that the answer will be no, but I am wondering if there’s any way to use C++11/14 closures as a callback function in FLTK.


With Qt, you can write, for example:

connect(
_p->gammaSlider,
&qtwidget::FloatSlider::valueChanged,
[this](float value)
{
timeline::Levels levels = _p->levels;
levels.gamma = value;
Q_EMIT levelsChanged(levels);
Q_EMIT levelsEnabledChanged(true);
});

This makes the code extremely clean and easy to follow, as the code for the slider is inside the closure.


Gonzalo Garramuno
ggar...@gmail.com




Antal Ispanovity

unread,
Oct 17, 2022, 7:39:30 AM10/17/22
to fltkg...@googlegroups.com
hi

Gonzalo Garramuno <ggar...@gmail.com> ezt írta (időpont: 2022. okt. 17., H, 13:15):
My guess is that the answer will be no, but I am wondering if there’s any way to use C++11/14 closures as a callback function in FLTK.

By "closure" you mean "lambda function"?
If you mean lambda function, then the answer is yes. At least I used lambda functions. I also find it cleaner than using standalone functions or static (class) member functions.
I think there was a limitation, but I can't recall what was it. Maybe there was something with the capture list. I think the capture list shall be empty, but it's possible that my memory fails.

With Qt, you can write, for example:

            connect(
                _p->gammaSlider,
                &qtwidget::FloatSlider::valueChanged,
                [this](float value)
                {
                    timeline::Levels levels = _p->levels;
                    levels.gamma = value;
                    Q_EMIT levelsChanged(levels);
                    Q_EMIT levelsEnabledChanged(true);
                });

This makes the code extremely clean and easy to follow, as the code for the slider is inside the closure.


Gonzalo Garramuno
ggar...@gmail.com




--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/fltkgeneral/1ADCB230-274E-4D13-A2C2-808E44999F39%40gmail.com.

Gonzalo Garramuno

unread,
Oct 17, 2022, 9:05:00 AM10/17/22
to fltkg...@googlegroups.com


> El 17 oct. 2022, a las 08:39, Antal Ispanovity <wea...@gmail.com> escribió:
>
> By "closure" you mean "lambda function"?
> If you mean lambda function, then the answer is yes. At least I used lambda functions. I also find it cleaner than using standalone functions or static (class) member functions.
> I think there was a limitation, but I can't recall what was it. Maybe there was something with the capture list. I think the capture list shall be empty, but it's possible that my memory fails.
>

Yes. I mean.a lambda function. The limitation from what I gather is that the capture list must be empty. But I cannot get the syntax right. Do you have an example? I am trying with this which does not work:

Fl_Button but( 10, 10, 50, 20, "Push" );
but.callback( [](Fl_Widget* w, void* d)
{
std::cerr << "Pushed" << std::endl;
}, NULL);


Gonzalo Garramuno
ggar...@gmail.com

Gonzalo Garramuno

unread,
Oct 17, 2022, 9:12:21 AM10/17/22
to fltkg...@googlegroups.com
Never mind. I got it to work. I was using fltk-config —compile which does not set the compiler to compile as cxx11/14. I switch to a CMakeLists.txt file with CMAKE_CXX_STANDARD set to 14 and it worked.


Gonzalo Garramuno
ggar...@gmail.com




wea...@gmail.com

unread,
Oct 17, 2022, 9:13:04 AM10/17/22
to fltk.general
On Monday, October 17, 2022 at 3:05:00 PM UTC+2 ggar...@gmail.com wrote:
Yes. I mean.a lambda function. The limitation from what I gather is that the capture list must be empty. But I cannot get the syntax right. Do you have an example? I am trying with this which does not work:

Fl_Button but( 10, 10, 50, 20, "Push" );
but.callback( [](Fl_Widget* w, void* d)
{
std::cerr << "Pushed" << std::endl;
}, NULL);

Actually this syntax works for me. I'm on (Artix) Linux with gcc 12.2.0. Clang 14.0.6 also works on Linux. The message is printed. What's the error? Do you have a compile error or a runtime error? Are you on windows or linux? If you're on windows then your standard out and standard error may be redirected. I'm not too familiar with windows, but you need to use a certain project type (console application) to use standard out and error streams, I think. Or maybe you need to reopen them. Dunno.

Gonzalo Garramuno
ggar...@gmail.com

Gonzalo Garramuno

unread,
Oct 17, 2022, 9:53:27 AM10/17/22
to fltkg...@googlegroups.com
Okay. But with std::function it is possible to have any capture you want!!!!. See this example:

======== closure.cpp

#include <iostream>
#include <functional>

#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl.H>

using FunctionCallback = std::function<void (Fl_Widget* w, void* d)>;

auto adapter = [](Fl_Widget* w, void* data)
{
FunctionCallback* pFunc = reinterpret_cast<FunctionCallback*>(data);
(*pFunc)(w, NULL);
};

int main( int argc, const char** argv )
{
int counter = 0;
Fl_Double_Window win(640, 400);
Fl_Button but( 10, 10, 50, 20, "Push" );

// Here we capture two things, the window and the counter!!!
auto callback_lambda = [&counter, &win](Fl_Widget* w, void* d){
std::cerr << counter << ") WxH=" << win.w() << "x" << win.h()
<< " w->label=" << w->label() << std::endl;
};

FunctionCallback callback_object = callback_lambda;

but.callback( adapter, &callback_object);
win.end();
win.show();
return Fl::run();
}

======
CMakeLists.txt

cmake_minimum_required(VERSION 3.21)

project( closure )

set( CMAKE_CXX_STANDARD 14 )
set( CMAKE_CXX_STANDARD_REQUIRED ON )
set( CMAKE_CXX_EXTENSIONS OFF )

find_package( FLTK NO_MODULE )

set( HEADERS
)

set(SOURCES
closure.cpp
)

include_directories( ${FLTK_INCLUDE_DIRS} )
link_directories( ${FLTK_LIBRARY_DIR} )

if( APPLE )
add_definitions( -D GL_SILENCE_DEPRECATION )
set( OSX_FRAMEWORKS "-framework Cocoa -framework IOKit" )
list( APPEND LIBRARIES ${OSX_FRAMEWORKS} )
endif()

add_executable(closure ${SOURCES} ${HEADERS})

list( APPEND LIBRARIES fltk )

target_link_libraries(closure ${LIBRARIES} )
set_target_properties(closure PROPERTIES FOLDER bin)
set_target_properties(closure PROPERTIES PUBLIC_HEADER "${HEADERS}”)

========

This is SUPER COOL!!!


Gonzalo Garramuno
ggar...@gmail.com

Mo_Al_

unread,
Oct 17, 2022, 4:24:50 PM10/17/22
to fltk.general
I used to define a Widget wrapper class, it allows passing a lambda and accessing to the underlying widget in the callback, without having to cast from Fl_Widget to another widget type.

#include <FL/Fl.H>
#include <FL/Enumerations.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Input.H>
#include <functional>

template<typename W, typename = typename std::enable_if<std::is_base_of<Fl_Widget, W>::value>::type>
class Widget: public W {
  std::function<void(W *)> fn_ = nullptr;
  public:
  Widget(int x, int y, int w, int h, const char *l = 0): W(x, y, w, h, l) {}
  void callback(const std::function<void(W *)> &fn) {
    fn_ = fn;
    auto shim = [](Fl_Widget *w, void *data) {
      auto f = (std::function<void(W *)> *)data;
      (*f)((W *)w);
    };
    W::callback(shim, (void *)&fn_);
  }
};

int main() {
  auto w = new Fl_Double_Window(300, 300);
  auto btn1 = new Widget<Fl_Button>(100, 100, 80, 30, "Click");
  btn1->callback([=](auto b) {
    b->label("Clicked"); // b refers to the button
    w->label("Button1 clicked");
  });
  auto btn2 = new Widget<Fl_Button>(100, 200, 80, 30, "Click");
  btn2->callback([=](auto b) {
    b->label("Clicked");
    w->label("Button2 clicked");
  });
  auto input = new Widget<Fl_Input>(100, 150, 80, 30, "");
  input->when(FL_WHEN_CHANGED);
  input->callback([](auto i) {
    puts(i->value()); // no need to cast the i to Fl_Inputs
  });
  w->end();
  w->show();
  return Fl::run();
}

This requires C++14

It can be used with C++11 if you change the `auto` in the lambda call to a specific type:
  input->callback([](Fl_Input *i) {
    puts(i->value()); // no need to cast the i to Fl_Inputs
  });

Gonzalo Garramuno

unread,
Oct 17, 2022, 10:34:35 PM10/17/22
to fltkg...@googlegroups.com


> El 17 oct. 2022, a las 17:24, Mo_Al_ <may64...@gmail.com> escribió:
>
> I used to define a Widget wrapper class, it allows passing a lambda and accessing to the underlying widget in the callback, without having to cast from Fl_Widget to another widget type.
>

That’s an amazing little template class! Is there a way to use it in members of a class without having to have two variables? This is what I am doing now:


#include <FL/Fl_Hor_Slider.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Float_Input.H>
#include <FL/Fl_Group.H>

#include “mrvFunctional.h”. // for Widget template class

class HorSlider : public Fl_Group
{
public:
Fl_Float_Input* uiValue;
Fl_Hor_Slider* uiSlider;
Fl_Button* uiReset;
double default_value;
public:
HorSlider( int X, int Y, int W, int H, const char* L = 0 ) :
Fl_Group( X, Y, W, H )
{
auto uiValueW = new Widget<Fl_Float_Input>( X+30, Y, 30, H, L );
uiValue = uiValueW;
auto uiSliderW = new Widget<Fl_Hor_Slider>( X+60, Y, W-70, H );
uiSlider = uiSliderW;
uiSlider->when(FL_WHEN_CHANGED);
auto uiResetW = new Widget<Fl_Button>( X+W-10, Y, 10, H, "@-31+" );
uiReset = uiResetW;
uiReset->box( FL_NO_BOX );
end();
resizable( uiSlider );

uiSliderW->callback([=](auto s) {
double v = s->value();
char buf[32];
snprintf( buf, 32, "%.2g", v );
uiValue->value( buf );
} );

uiValueW->callback( [=]( auto o ) {
double v = atof( o->value() );
uiSlider->value( v );
} );

uiResetW->callback( [=]( auto o ) {
uiSlider->value( default_value );
uiSlider->do_callback();
} );
}

// other methods go here...
};


Gonzalo Garramuno
ggar...@gmail.com




Mo_Al_

unread,
Oct 18, 2022, 5:44:49 AM10/18/22
to fltk.general
You can either just perform the cast before calling the callback function:
class HorSlider : public Fl_Group {
public:
Fl_Float_Input *uiValue;
Fl_Hor_Slider *uiSlider;
Fl_Button *uiReset;
double default_value;

public:
HorSlider(int X, int Y, int W, int H, const char *L = 0)
: Fl_Group(X, Y, W, H) {
uiValue = new Widget<Fl_Float_Input>(X + 30, Y, 30, H, L);
uiSlider = new Widget<Fl_Hor_Slider>(X + 60, Y, W - 70, H);
uiSlider->when(FL_WHEN_CHANGED);
uiReset = new Widget<Fl_Button>(X + W - 10, Y, 10, H, "@-31+");
uiReset->box(FL_NO_BOX);
end();
resizable(uiSlider);

((Widget<Fl_Hor_Slider> *)uiSlider)->callback([=](auto s) {
double v = s->value();
char buf[32];
snprintf(buf, 32, "%.2g", v);
uiValue->value(buf);
});

((Widget<Fl_Float_Input> *)uiValue)->callback([=](auto o) {
double v = atof(o->value());
uiSlider->value(v);
});

((Widget<Fl_Button> *)uiReset)->callback([=](auto o) {
uiSlider->value(default_value);
uiSlider->do_callback();
});
}

// other methods go here...
};


Or you can store Widget pointers in your class:
class HorSlider : public Fl_Group {
public:
Widget<Fl_Float_Input> *uiValue;
Widget<Fl_Hor_Slider> *uiSlider;
Widget<Fl_Button> *uiReset;
double default_value;

public:
HorSlider(int X, int Y, int W, int H, const char *L = 0)
: Fl_Group(X, Y, W, H) {
uiValue = new Widget<Fl_Float_Input>(X + 30, Y, 30, H, L);
uiSlider = new Widget<Fl_Hor_Slider>(X + 60, Y, W - 70, H);
uiSlider->when(FL_WHEN_CHANGED);
uiReset = new Widget<Fl_Button>(X + W - 10, Y, 10, H, "@-31+");
uiReset->box(FL_NO_BOX);
end();
resizable(uiSlider);

uiSlider->callback([=](auto s) {
double v = s->value();
char buf[32];
snprintf(buf, 32, "%.2g", v);
uiValue->value(buf);
});

uiValue->callback([=](auto o) {
double v = atof(o->value());
uiSlider->value(v);
});

uiReset->callback([=](auto o) {
uiSlider->value(default_value);
uiSlider->do_callback();
});
}

// other methods go here...
};


Reply all
Reply to author
Forward
0 new messages