ArthurSonzogni / FTXUI

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

Last scroll position of the vertical/horizontal container being reset when it's out of focus. #935

Open s0nx opened 1 month ago

s0nx commented 1 month ago

Let's say we have a vertical split and both top and bottom components are of Container::Vertical type. If we added some child components which are Container::Vertical / Container::Horizontal themselves to the top or bottom parent components, the current scroll position resets to 0 when the parent component is not focused.

Here is the simple reproducer:


#include <random>
#include <algorithm>

#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/screen.hpp>

#include <ftxui/component/component.hpp>
#include <ftxui/component/component_base.hpp>
#include <ftxui/component/event.hpp>
#include <ftxui/component/mouse.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/canvas.hpp>
#include <ftxui/screen/color.hpp>
#include <string>

using namespace ftxui;

int main()
{
    int size = 40;
    int size2 = 40;

    auto top_comp    = Container::Vertical({});
    auto bottom_comp = Container::Vertical({});
    bottom_comp->Add(Button("exit", []{}));

    for (int i = 0; i < 30; i++)
    {
        if (i % 2 == 0)
        {
            top_comp->Add(Button("Button #" + std::to_string(i), []{}));
        }
        else
        {
            auto hor_button_comp = Container::Horizontal({
                Button("Button #" + std::to_string(i), []{}),
                Button("nottuB #" + std::to_string(i * 2), []{}),
            });
            top_comp->Add(hor_button_comp);
        }
    }

    auto rendered_top_comp = Renderer(top_comp, [&] {
        return top_comp->Render() | vscroll_indicator | hscroll_indicator | frame;
    });

    auto vert_split_comp = ResizableSplitTop(rendered_top_comp, bottom_comp, &size2);

    auto right_comp = Container::Horizontal({});
    right_comp->Add(Button("right split button 1", []{}));
    right_comp->Add(Button("right split button 2", []{}));

    auto hor_split_comp = ResizableSplitRight(right_comp, vert_split_comp, &size);

    auto screen = ftxui::ScreenInteractive::Fullscreen();
    screen.Loop(hor_split_comp);
}

That's not happening when child component is not a container itself (Button for example). I've tried to capture the problem: ftxui-embed-container-scroll-issue

When i move the focus from Nottub #30 button to right split button, the vertical scroller moves all the way to the top. That's not happening if top_comp consists of Button components only. The scroller stays in place.

ArthurSonzogni commented 1 month ago

Thanks for the reproducer!

I believe the problem is that ftxui/dom was made without knowing ftxui/component will be something.

The hbox selection is computed by:

  void ComputeRequirement() override {
    requirement_.min_x = 0;
    requirement_.min_y = 0;
    requirement_.flex_grow_x = 0;
    requirement_.flex_grow_y = 0;
    requirement_.flex_shrink_x = 0;
    requirement_.flex_shrink_y = 0;
    requirement_.selection = Requirement::NORMAL;
    for (auto& child : children_) {
      child->ComputeRequirement();
      if (requirement_.selection < child->requirement().selection) {
        requirement_.selection = child->requirement().selection;
        requirement_.selected_box = child->requirement().selected_box;
        requirement_.selected_box.x_min += requirement_.min_x;
        requirement_.selected_box.x_max += requirement_.min_x;
      }
      requirement_.min_x += child->requirement().min_x;
      requirement_.min_y =
          std::max(requirement_.min_y, child->requirement().min_y);
    }
  }

So, we are keeping the selection of the child with the largest priority among focused and selected.

In your case, none of the button are focused, but they are all selected. So, the frame is focusing the first one.

The Container::Horizontal doesn't really specify which of the children must be prioritized.

ArthurSonzogni commented 1 month ago

I will be very busy this month. If you have some propositions or want to submit a PR, we can discuss them if you are interested.