ArthurSonzogni / FTXUI

:computer: C++ Functional Terminal User Interface. :heart:
MIT License
6.94k stars 418 forks source link

Paragraph input based on currently focused element #489

Closed breyerml closed 2 years ago

breyerml commented 2 years ago

Is there a way to change the content of a paragraph element based on the currently focused (not selected) checkbox? (The intend of use is to provide additional information to the user before he clicks the checkbox.)

I already have a MWE (or better a Minimal "Not-So-Working" Example):

#include <memory>  // for allocator, __shared_ptr_access
#include <string>  // for char_traits, operator+, string, basic_string
#include <chrono>

#include "ftxui/component/captured_mouse.hpp"  // for ftxui
#include "ftxui/component/component.hpp"       // for Input, Renderer, Vertical
#include "ftxui/component/component_base.hpp"  // for ComponentBase
#include "ftxui/component/component_options.hpp"  // for InputOption
#include "ftxui/component/screen_interactive.hpp"  // for Component, ScreenInteractive
#include "ftxui/dom/elements.hpp"  // for text, hbox, separator, Element, operator|, vbox, border
#include "ftxui/util/ref.hpp"  // for Ref

using namespace ftxui;

std::string info;

int main(int argc, const char* argv[]) {

  auto screen = ScreenInteractive::Fullscreen();

  // additional options
  bool checkbox1_checked = false;
  CheckboxOption opt = CheckboxOption::Simple();
  opt.transform = [&](EntryState state) {
    if (state.focused) {
      info = "Some useful information for checkbox1";
    } else {
      info = "";
    }
    return hbox({ filler(), text(state.label)} );
  };
  auto checkbox1 = Checkbox("checkbox1", &checkbox1_checked, opt);

  bool checkbox0_checked = false;
  bool checkbox2_checked = false;
  auto options = Container::Vertical({
    Checkbox("checkbox0", &checkbox0_checked),
    checkbox1,
    Checkbox("checkbox2", &checkbox2_checked)
  });

  // paragraph
  auto para = Container::Horizontal({});
  auto para_renderer = Renderer(para, [&]() {
    return hflow(paragraph(info)) | border | size(WIDTH, EQUAL, 100);
  });

  auto global = Container::Vertical({
    options,
    para_renderer
  }) | size(WIDTH, EQUAL, 100) | center;

  screen.Loop(global);

  return 0;
}

With this code updating the paragraph works as intended. However, the transform function must return an Element and I have no idea how to return a meaningful object that does not destroy the checkbox completely. Currently, e.g., the actual checkbox "icon" is erased.

Is there a way to update the paragraph and retain the checkbox "icon"? (On a side note: is it possible to do the same thing on a single Radiobox and not a complete Radiobox "group" (I don't think so, but that wouldn't be that much of a problem, if it at least works on a Radiobox "group".).)

ArthurSonzogni commented 2 years ago

Welcome!

I gave it a try here, with some comments. Could you please take a look at let me know what you think of it?

int main(int argc, const char* argv[]) {
  // State: 
  bool checkbox0_checked = false;
  bool checkbox1_checked = false;
  bool checkbox2_checked = false;

  // Some components:
  auto checkbox0 = Checkbox("checkbox0", &checkbox0_checked);
  auto checkbox1 = Checkbox("checkbox1", &checkbox1_checked);
  auto checkbox2 = Checkbox("checkbox2", &checkbox2_checked);

  // Define how to navigate in between the components:
  auto layout = Container::Vertical({
      checkbox0,
      checkbox1,
      checkbox2,
  });

  // Override how to render the layout:
  auto presentation = Renderer(layout, [&] {
    auto paragraph_0 = checkbox0_checked
                           ? text("Some useful information for checkbox1")
                           : text("Nothing");
    auto paragraph_1 = checkbox1_checked
                           ? text("Some useful information for checkbox1")
                           : text("Nothing");
    auto paragraph_2 = checkbox2_checked
                           ? text("Some useful information for checkbox1")
                           : text("Nothing");

    auto checkboxes = vbox({
        hbox(checkbox0->Render(), paragraph_0),
        hbox(checkbox1->Render(), paragraph_1),
        hbox(checkbox2->Render(), paragraph_2),
    });

    // Misc:
    checkboxes |= size(WIDTH, EQUAL, 100);
    checkboxes |= center;

    return checkboxes;
  });

  auto screen = ScreenInteractive::Fullscreen();
  screen.Loop(presentation);

  return 0;
}

(I haven't tried compiling it, some errors might exist)

breyerml commented 2 years ago

Thanks for the fast reply! As far as I understand, the checkbox?_checked variables are only going to be true if the user checks the checkbox. So the information only updates after checking the checkbox. What I would like to achieve is that the information is updated if the respective checkbox is selected or focused, i.e., before someone checks the checkbox. (The information should help the user to decide whether he has to check the respective checkbox. So it wouldn't be very helpful if the user has to check the checkbox first to get that information.)

ArthurSonzogni commented 2 years ago

I see!

Maybe you can call the default implementation after you updated the info? https://github.com/ArthurSonzogni/FTXUI/blob/c61fadd8eca94599dbcad48ded962cc0373ee966/src/ftxui/component/component_options.cpp#L216-L232

In your case it gives:

  opt.transform = [&](EntryState state) {
    if (state.focused) {
      info = "Some useful information for checkbox1";
    } else {
      info = "";
    }
    return CheckboxOption::Simple().transform(state);
  };
breyerml commented 2 years ago

Thanks that works like a charm!