ArthurSonzogni / FTXUI

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

Focus does not account for the extra height when using longer paragraphs in yframe #399

Open BlackEdder opened 2 years ago

BlackEdder commented 2 years ago

I am hoping to use ftxui in a console app I have been working on, but scrolling (yframe) behaves unexpectedly. If you try the code below, I would expect the focussed element in paragraph_renderer_left to always be visible, but instead as I scroll down, the focussed element goes one step down. I believe this might be due to the fact that my paragraph text takes two lines, while it expects one line of text? Am I misunderstanding something here?

Note that if you try the code below and you only see one line of text per element (because you have a wide screen), then you might need to resize your terminal and cause it to use multiple lines.

#include <chrono>
#include <iostream>
#include <thread>

#include "ftxui/component/component.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/dom/elements.hpp"
#include "ftxui/dom/flexbox_config.hpp"

std::vector<std::string> messages;

int main(void) {
  auto screen = ftxui::ScreenInteractive::Fullscreen();

  size_t focus_id = 0;

  auto paragraph_renderer_left = ftxui::Renderer([&] {
    using namespace ftxui;

    std::string str =
        "Lorem Ipsum is simply dummy text of the printing and typesetting "
        "industry. Lorem Ipsum has been the industry's standard dummy text "
        "ever since the 1500s, when an unknown printer took a galley of type "
        "and scrambled it to make a type specimen book. 😁";
    Elements elements;
    size_t i = 0;
    for (; i < 50; ++i) {
      if (i == focus_id) {
        elements.push_back(border(paragraphAlignLeft(str)) | focus | bold);
      } else {
        elements.push_back(border(paragraphAlignLeft(str)));
      }
    }

    messages.push_back("Focus id: " + std::to_string(focus_id) + " " +
                       std::to_string(i));

    return vbox(elements) | yframe | flex;
  });

  // Do I really need a renderer?
  // TODO: Use maybe to only render this when needed!
  auto messages_renderer = ftxui::Renderer([&] {
    using namespace ftxui;

    Elements elements{};
    for (size_t i = 0; i < messages.size(); ++i) {
      std::string pre = std::to_string(i) + ". ";
      if (i + 1 == messages.size()) {
        elements.push_back(paragraphAlignLeft(pre + messages[i]) | focus | bold);
      } else {
        elements.push_back(paragraphAlignLeft(pre + messages[i]));
      }
    }

    return vbox({text("Messages: " + std::to_string(messages.size())) | bold,
                 vbox(elements) | yframe | size(HEIGHT, EQUAL, 8)});
  });

  auto main_container =
      ftxui::Container::Vertical({paragraph_renderer_left, messages_renderer});

  auto main_renderer = Renderer(main_container, [&] {
    return ftxui::vbox(
        {ftxui::text("FTXUI Demo") | ftxui::bold | ftxui::hcenter,
         paragraph_renderer_left->Render(), messages_renderer->Render()});
  });

  auto component = CatchEvent(main_renderer, [&](ftxui::Event event) {
    if (event == ftxui::Event::Character('q')) {
      screen.ExitLoopClosure()();
      return true;
    } else if (event == ftxui::Event::Character('j')) {
      ++focus_id;
      return true;
    } else if (event == ftxui::Event::Character('k')) {
      if (focus_id > 0)
        --focus_id;
      return true;
    }
    return false;
  });

  bool refresh_ui_continue = true;
  std::thread refresh_ui([&] {
    while (refresh_ui_continue) {
      using namespace std::chrono_literals;
      std::this_thread::sleep_for(0.05s);
    }
  });

  // screen.Loop(paragraph_renderer_left);
  screen.Loop(component);
  refresh_ui_continue = false;
  refresh_ui.join();

  return EXIT_SUCCESS;
}
ArthurSonzogni commented 2 years ago

Thanks for providing an example. I will take a look!

ArthurSonzogni commented 2 years ago

I am trying a fix here: https://github.com/ArthurSonzogni/FTXUI/pull/405

BlackEdder commented 2 years ago

Thank you for the fast fix. I ended up going with a custom solution, because I have need of knowing the height of each element, but good to know I could fall back on this method.

Do you know what will happen if the flexbox that you set focus on is larger than the screen?

ArthurSonzogni commented 2 years ago

need of knowing the height of each element, but good to know I could fall back on this method. You can get this information by using:

Box box;
auto document = element | reflect(&box);
screen.Render(document);
// `box` contains the dimensions of element as it was printed on the screen.

Do you know what will happen if the flexbox that you set focus on is larger than the screen? The implementation is located here: https://github.com/ArthurSonzogni/FTXUI/blob/master/src/ftxui/dom/frame.cpp#L100-L108

but I can't really say what happens just by reading the code. We must try and verify.