ArthurSonzogni / FTXUI

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

Frame is not scrollable for texts #443

Closed fridenmf closed 2 years ago

fridenmf commented 2 years ago

Using the following code snippet you can draw a scrollable frame of buttons:

auto components = ftxui::Container::Vertical({});
for (auto i = 0, s = 10; i < s; ++i)
{
    auto component = ftxui::Button(std::string("button ") + std::to_string(i), []() {});
    components->Add(component);
}

auto renderer = ftxui::Renderer(components, [&]
{
    return components->Render()
        | ftxui::vscroll_indicator
        | ftxui::frame
        | ftxui::borderLight
        | ftxui::size(ftxui::WIDTH, ftxui::LESS_THAN, 24)
        | ftxui::size(ftxui::HEIGHT, ftxui::LESS_THAN, 6);
});

auto screen = ftxui::ScreenInteractive::FixedSize(80, 25);

screen.Loop(renderer);

Looking like this when started:

┌────────────────────────┐
│┌──────────────────────┃│
││button 0              ╹│
│└────────────────────── │
│┌────────────────────── │
││button 1               │
│└────────────────────── │
└────────────────────────┘

And like this when scrolled to the bottom:

┌────────────────────────┐
│┌────────────────────── │
││button 8               │
│└────────────────────── │
│┌────────────────────── │
││button 9              ╻│
│└──────────────────────┃│
└────────────────────────┘

But when replacing the component creation from buttons to texts:

auto component = ftxui::Renderer([=]() { return ftxui::text(std::string("text ") + std::to_string(i)) | ftxui::border; });

It starts correctly like this:

┌────────────────────────┐
│╭──────────────────────┃│
││text 0                ╹│
│╰────────────────────── │
│╭────────────────────── │
││text 1                 │
│╰────────────────────── │
└────────────────────────┘

But it's not scrollable. Is this a bug or is there any known workarounds?

And as a side note, is there a more convenient way to add elements to components like the vertical container other than creating a Renderer just returning the element? This is a very minimal reproducing code snippet but our full code base is full of "Renderer wrappers" just to fit things into interactable containers.

Thanks in advance

ArthurSonzogni commented 2 years ago

It is because after transforming the UI into Element, we don't know which part of the frame to put the focus on. One of them need to use the focus decorator.

There is an alternative function for Renderer providing you a boolean telling you if the element is the one focused. So, you can use this:

auto component = ftxui::Renderer([=](bool focused) {
  auto element =
      ftxui::text(std::string("text ") + std::to_string(i)) | ftxui::border;

  if (focused)
    element |= focus;
  return element;
});

This is what Button is using: https://github.com/ArthurSonzogni/FTXUI/blob/d9241435ce9fa3d81f0ea5a0d34c3048883629ee/src/ftxui/component/button.cpp#L82

And as a side note, is there a more convenient way to add elements to components like the vertical container other than creating a Renderer just returning the element? This is a very minimal reproducing code snippet but our full code base is full of "Renderer wrappers" just to fit things into interactable containers.

The default is to create a class implementing ComponentBase. The renderer function is just an utility to avoid you the hassle to define one.

A Component evolve its state when receiving inputs. It renders Element. Then the elements are like the DOM of an HTML document. They are used to draw a single frame into the Screen. We can't use an Element for multiple frame. They need to be recreated every time.

fridenmf commented 2 years ago

Aah, I get it. Thanks for the quick response, the focused lambda overload works great!