c++user-interfacec++11callbacklibigl

Dynamic number of GUI sliders which update an std::vector with values in callbacks


I'm trying to add a dynamic number of sliders to a GUI window, and update an std::vector<float> with the values according to how each slider changes. The GUI library uses callbacks and I can successfully do it for one slider (or when copy&pasting the slider code 20 times but that's not a solution), however I'm having problems factoring the slider creation into a function: There are issues with variable lifetime in how to tell each slider which element in the vector it should update.

I'm using nanogui (or libigl specifically, which uses nanogui) but I think the problem is actually quite generic and should apply to other GUI frameworks or situations as well.

Here's a short version of the code of how to add one slider and it updates my_values[0] (note I had to put "0" manually):

int main(int argc, char *argv[])
{
  igl::viewer::Viewer viewer;
  std::vector<float> my_values(50);
  viewer.callback_init = [&](igl::viewer::Viewer& viewer)
    {
    nanogui::Slider* slider = new nanogui::Slider(viewer.ngui->window());

    slider->setCallback([&](float value) {
      my_values[0] = value; // this slider sets the value at element '0'
      });

    viewer.ngui->addWidget("0", slider);

    viewer.screen->performLayout();
    return false;
  };

  viewer.launch();
}

Now as mentioned before, in viewer.callback_init, I'd now like to add 50 of these sliders but obviously I don't want to copy&paste the slider and its callback code 50 times. But somehow I need to pass my_values[i], i.e. which element in the vector each slider is going to update. I tried something like this which doesn't work, because at the time that the function/callback is actually called, the variable value_id is not defined:

int main(int argc, char *argv[])
{
  igl::viewer::Viewer viewer;
  std::vector<float> my_values(50);

  auto add_slider = [&](igl::viewer::Viewer& viewer, std::vector<float>& my_values, int value_id, std::string value_name)
    {   
    nanogui::Slider* slider = new nanogui::Slider(viewer.ngui->window());

    slider->setCallback([&](float value) {
      my_values[value_id] = value; // offending line, 'value_id' is not initialized
      // also I'm using 'value_name' to give the slider a name - code omitted
      });
    return slider;
  };

  viewer.callback_init = [&](igl::viewer::Viewer& viewer) {

    // Now add all 50 sliders (in a for-loop in real code obviously):
    viewer.ngui->addWidget("0", add_slider(viewer, 0, "0"));
    viewer.ngui->addWidget("1", add_slider(viewer, 1, "1"));
    // etc.

    viewer.screen->performLayout();
    return false;
  };

  viewer.launch();
}

I tried various things, making value_id a (const) reference, even an std::shared_ptr<int> (I know, horrible), but still value_id is always not initialized on the line my_values[value_id] = value;.

How can I accomplish this? My guess is it must be a well-known pattern/solution in GUI/callback programming, I just haven't figured it out yet.


Solution

  • Instead of storing slider values in a std::vector<float>, use map<string, float> or map<Slider*, float>. Therefore, whenever your callback is called, you'll know which variable to update(by the address or name of the slider).

    map<Slider*, float> my_values;
    slider->setCallback([&](float value) {
          my_values[slider] = value; 
          });