FLUID: Add support for lambda callbacks.
Build and Test / build-linux (push) Has been cancelled
Build and Test / build-wayland (push) Has been cancelled
Build and Test / build-macos (push) Has been cancelled
Build and Test / build-windows (push) Has been cancelled

Starting the callback text with a '[' assumes that
the rest of the callback is a lambda and generates
inlined code for it.
This commit is contained in:
Matthias Melcher
2025-12-22 23:12:25 +01:00
parent d0d2e104e9
commit 33199dab78
5 changed files with 55 additions and 19 deletions
+4 -5
View File
@@ -552,11 +552,10 @@ int xyz_data;
button->callback(xyz_callback, &xyz_data); button->callback(xyz_callback, &xyz_data);
\endcode \endcode
\note You cannot delete a widget inside a callback, as the It's also possible to use a non-capturing lambda function directly, for example:
widget may still be accessed by FLTK after your callback ```
is completed. Instead, use the Fl::delete_widget() button->callback( [](Fl_Widget*, void*) { puts("Hello"); } , nullptr );
method to mark your widget for deletion when it is safe ```
to do so.
Many programmers new to FLTK or C++ try to use a Many programmers new to FLTK or C++ try to use a
non-static class method instead of a static class method non-static class method instead of a static class method
+10 -5
View File
@@ -369,13 +369,18 @@
\image html wp_cpp_callback.png \image html wp_cpp_callback.png
\image latex wp_cpp_callback.png "" width=7cm \image latex wp_cpp_callback.png "" width=7cm
The callback field can be interpreted in two ways. If the callback text is only The callback field can be interpreted in three ways. If the callback text is
a single word, FLUID assumes that this is the name of an external callback only a single word, FLUID assumes that this is the name of an external
function and declares it in the header as callback function and declares it in the header as
`extern void my_button_action(Fl_Button*, void*);`. `extern void my_button_action(Fl_Button*, void*);`.
Otherwise, FLUID assumes that the text is the body of a C++ callback function If the first letter of the callback text is a '[', FLUID expects a lambda
and instead creates a local static callback function. The name of the callback function which will be inlined into the widget creation code. The lambda
signature must be `[](Fl_Widget*, void*)->void { ... }`. The widget pointer
can be casted to another type inside the lambda.
Otherwise, FLUID assumes that the text is the body of a C++ function,
and a local static callback function is created. The name of the callback
function is generated by FLUID and guaranteed to be unique within the file. function is generated by FLUID and guaranteed to be unique within the file.
``` ```
static void cb_input(Fl_Input *o, void *v) { static void cb_input(Fl_Input *o, void *v) {
+15 -6
View File
@@ -329,7 +329,7 @@ void Menu_Item_Node::write_static(fld::io::Code_Writer& f) {
if (extra_code(n) && isdeclare(extra_code(n))) if (extra_code(n) && isdeclare(extra_code(n)))
f.write_h_once("%s", extra_code(n)); f.write_h_once("%s", extra_code(n));
} }
if (callback() && !is_name(callback())) { if (callback() && !is_name(callback()) && (callback()[0] != '[')) {
// see if 'o' or 'v' used, to prevent unused argument warnings: // see if 'o' or 'v' used, to prevent unused argument warnings:
int use_o = 0; int use_o = 0;
int use_v = 0; int use_v = 0;
@@ -520,11 +520,20 @@ void Menu_Item_Node::write_item(fld::io::Code_Writer& f) {
f.write_c(", 0, "); f.write_c(", 0, ");
} }
if (callback()) { if (callback()) {
const char* k = is_name(callback()) ? nullptr : class_name(1); if (callback()[0] == '[') {
if (k) { f.write_c("\n");
f.write_c(" (Fl_Callback*)%s::%s,", k, callback_name(f)); f.tag(Mergeback::Tag::GENERIC, Mergeback::Tag::WIDGET_CALLBACK, 0);
f.write_c_indented(callback(), 1, 0);
f.write_c("\n");
f.tag(Mergeback::Tag::WIDGET_CALLBACK, Mergeback::Tag::GENERIC, get_uid());
f.write_c("%s, ", f.indent_plus(1));
} else { } else {
f.write_c(" (Fl_Callback*)%s,", callback_name(f)); const char* k = is_name(callback()) ? nullptr : class_name(1);
if (k) {
f.write_c(" (Fl_Callback*)%s::%s,", k, callback_name(f));
} else {
f.write_c(" (Fl_Callback*)%s,", callback_name(f));
}
} }
} else } else
f.write_c(" 0,"); f.write_c(" 0,");
@@ -571,7 +580,7 @@ void Menu_Item_Node::write_code1(fld::io::Code_Writer& f) {
} }
if (callback()) { if (callback()) {
if (!is_name(callback()) && class_name(1)) { if (!is_name(callback()) && (callback()[0] != '[') && class_name(1)) {
const char* cn = callback_name(f); const char* cn = callback_name(f);
const char* ut = user_data_type() ? user_data_type() : "void*"; const char* ut = user_data_type() ? user_data_type() : "void*";
f.write_public(0); f.write_public(0);
+11 -2
View File
@@ -1499,7 +1499,7 @@ void Widget_Node::write_static(fld::io::Code_Writer& f) {
if (strchr(c, '[') == nullptr) f.write_c("%s *%s=(%s *)0;\n", t, c, t); if (strchr(c, '[') == nullptr) f.write_c("%s *%s=(%s *)0;\n", t, c, t);
else f.write_c("%s *%s={(%s *)0};\n", t, c, t); else f.write_c("%s *%s={(%s *)0};\n", t, c, t);
} }
if (callback() && !is_name(callback())) { if (callback() && !is_name(callback()) && (callback()[0] != '[')) {
// see if 'o' or 'v' used, to prevent unused argument warnings: // see if 'o' or 'v' used, to prevent unused argument warnings:
int use_o = 0; int use_o = 0;
int use_v = 0; int use_v = 0;
@@ -1883,7 +1883,16 @@ void Widget_Node::write_widget_code(fld::io::Code_Writer& f) {
const char* ud = user_data(); const char* ud = user_data();
if (class_name(1) && !parent->is_widget()) ud = "this"; if (class_name(1) && !parent->is_widget()) ud = "this";
if (callback()) { if (callback()) {
f.write_c("%s%s->callback((Fl_Callback*)%s", f.indent(), var, callback_name(f)); if (callback()[0] == '[') {
f.write_c("%s%s->callback(\n", f.indent(), var);
f.tag(Mergeback::Tag::GENERIC, Mergeback::Tag::WIDGET_CALLBACK, 0);
f.write_c_indented(callback(), 1, 0);
f.write_c("\n");
f.tag(Mergeback::Tag::WIDGET_CALLBACK, Mergeback::Tag::GENERIC, get_uid());
f.write_c("%s", f.indent_plus(1));
} else {
f.write_c("%s%s->callback((Fl_Callback*)%s", f.indent(), var, callback_name(f));
}
if (ud) if (ud)
f.write_c(", (void*)(%s));\n", ud); f.write_c(", (void*)(%s));\n", ud);
else else
+15 -1
View File
@@ -7,6 +7,7 @@ i18n_gnu_function gettext
i18n_gnu_static_function gettext_noop i18n_gnu_static_function gettext_noop
header_name {.h} header_name {.h}
code_name {.cxx} code_name {.cxx}
include_guard {}
comment {About test/preferences: comment {About test/preferences:
The preferences app shows two features of FLTK and FLUID. The preferences app shows two features of FLTK and FLUID.
@@ -184,6 +185,10 @@ Fl_Input::paste_menu_text = gettext("Paste");} {}
} }
MenuItem {} { MenuItem {} {
label sandals label sandals
callback {[](Fl_Widget*, void*)
{
puts("The shoe is the sign!");
}} selected
xywh {0 0 100 20} xywh {0 0 100 20}
} }
MenuItem {} { MenuItem {} {
@@ -203,7 +208,7 @@ Fl_Input::paste_menu_text = gettext("Paste");} {}
xywh {35 112 100 24} type Radio down_box ROUND_DOWN_BOX xywh {35 112 100 24} type Radio down_box ROUND_DOWN_BOX
} }
Fl_Round_Button wRight { Fl_Round_Button wRight {
label {right side} selected label {right side}
xywh {35 136 100 24} type Radio down_box ROUND_DOWN_BOX xywh {35 136 100 24} type Radio down_box ROUND_DOWN_BOX
} }
Fl_Box {} { Fl_Box {} {
@@ -217,6 +222,15 @@ Fl_Input::paste_menu_text = gettext("Paste");} {}
} }
Fl_Check_Button wShave { Fl_Check_Button wShave {
label shave label shave
callback {[](Fl_Widget* w, void*)->void {
auto* btn = static_cast<Fl_Check_Button*>(w);
if (btn->value()) {
puts("Shave.");
} else {
puts("Don't shave.");
}
}}
comment {// Testing lambdas for callbacks}
xywh {25 212 105 24} down_box DOWN_BOX xywh {25 212 105 24} down_box DOWN_BOX
} }
Fl_Check_Button wBrush { Fl_Check_Button wBrush {