Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FTXUI multiple logging windows #14

Closed
Matheus-Garbelini opened this issue Apr 10, 2020 · 6 comments
Closed

FTXUI multiple logging windows #14

Matheus-Garbelini opened this issue Apr 10, 2020 · 6 comments

Comments

@Matheus-Garbelini
Copy link

Hi, thanks a lot for the library.
I was looking at the examples and wonder if it's possible to create multiple windows which are used solely show log information.

In short, I want to log multiple information in different windows. Is there an optimal design using this library to achieve that?

The maximum I could find is two use two windows, each one having a generic element inside which I have to keep updating the text:

Elements line;
line.push_back(text(L"possibly string stream here") | bold);

However, "text" element doesn't seem to support multiple lines with "\n".
Is there a suggestion on how to do that?

Regards.

@Matheus-Garbelini Matheus-Garbelini changed the title FTXUI multiple logger windows FTXUI multiple logging windows Apr 10, 2020
@ArthurSonzogni
Copy link
Owner

ArthurSonzogni commented Apr 10, 2020

Hi, thank for using the library!
I would be happy to help you and see what you are going to build with it.

I am not sure to understand what you call a windows.

If you want to put several element together vertically, you can use a vertical box => vbox
If might also be interested into the "paragraph" element

Maybe the "package manager" example could be what you want:
https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/dom/package_manager.cpp
https://www.youtube.com/watch?v=x0q-olsPgNg

Maybe the starter project could be close to what you want:
https://github.com/ArthurSonzogni/ftxui-starter
asciicast

@Matheus-Garbelini
Copy link
Author

Matheus-Garbelini commented Apr 10, 2020

@ArthurSonzogni Thanks for the fast response.
In summary, my application writes a lot of information to the console via cout.
I just want to be able to write these debugging messages in different boxes.
i.e.,I think the best word to describe is that I'm trying to create a multiline textbox just to print fast information.

@ArthurSonzogni
Copy link
Owner

ArthurSonzogni commented Apr 10, 2020

I see!

I think you can keep track in "n" vector of the lines you want draw.

Let call this your "model":

std::vector<std::wstring> data_box_1;
std::vector<std::wstring> data_box_2;
std::vector<std::wstring> data_box_3

Let's create a function to render your model:

auto render = [&](){
  std::vector<Element> box_1;
  for(auto& line : data_box_1)
    entries.push_back(text(line))
   
  // [...] Same for box_2 and box_3.

  return
    hbox(
      window(text(L" box_1 "), vbox(std::move(box_1))),
      window(text(L" box_2 "), vbox(std::move(box_2))),
      window(text(L" box_3 "), vbox(std::move(box_3)))
     );
  };

Then let' create a function to refresh the screen. You can call it everytime you update your model.

std::string reset_position;
auto refresh = [&](){
  auto document = render();
  auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
  Render(screen, document.get());
  std::cout << reset_position << screen.ToString() << std::flush;
  reset_position = screen.ResetPosition();
};

You can use the example:
https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/dom/package_manager.cpp

@ArthurSonzogni
Copy link
Owner

If you want your application to respond to the user event you have to use the components from the "component" directory.
If you simply want to display (potentially periodically) a document from you application, you have to use the elements from the "dom" directory. <= I guess this is what you are doing right now. Great!

@Matheus-Garbelini
Copy link
Author

Matheus-Garbelini commented Apr 11, 2020

@ArthurSonzogni Thanks a lot. I had to extend such a template to create a generic logger.
My main concern is real-time logging. Moreover, I had to limit the list of entries to a ring buffer, otherwise, there's no end to messages being printed. With this new class, you can just call: WindowLogger.LOG("Value: ", rand(), <variarg what you want>);
It adds your messages to an internal wstring vector. Then, you can just call auto document = WindowLogger.element() to return the updated dom.

Check the result in asciinema: https://asciinema.org/a/318630 or in the gif below.

real_time_logger

This sort of reactiveness could be useful for more real-time or dynamic components on FTXUI. Particularly for applications which do not have user interaction, but you need APIs to easily interact with the UI components.

The full code with this custom component is shown below. Regards and thanks again.

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include <sstream>
#include <iomanip>

#include "ftxui/dom/elements.hpp"
#include "ftxui/screen/screen.hpp"
#include "ftxui/screen/string.hpp"

using namespace std::chrono_literals;
using namespace ftxui;
using namespace std;

class WindowLogger
{
private:
  struct MSGS
  {
    wstring msg;
    Color msg_color;
  };

  vector<Element> line_elements;
  vector<MSGS> line_msgs;
  wstringstream string_converter;
  wstring name;
  uint16_t max_lines;
  bool enable_flex;

  inline auto text_element(wstring msg, Color text_color)
  {
    if (text_color != Color::Default)
      return hbox(text(msg) | color(text_color));
    else
      return text(msg);
  }

  template <class... T>
  inline void add_msg(Color msg_color, T &&... args)
  {
    string_converter.seekp(ios::beg); // Reset string stream
    ((string_converter << forward<T>(args)), ...);

    if (line_msgs.size() < max_lines)
    {
      line_msgs.push_back({string_converter.str(), msg_color});
    }
    else
    {
      rotate(line_msgs.begin(), line_msgs.begin() + 1, line_msgs.end());
      // insert new text at the last line
      line_msgs[line_msgs.size() - 1] = {string_converter.str(), msg_color};
    }
  }

public:
  WindowLogger(const char *window_name, uint16_t max_lines = 1, bool fill_lines = false, bool enable_flex = false)
  {
    wstringstream s;
    s << window_name;
    name = s.str();
    this->max_lines = max_lines;
    line_elements.reserve(max_lines);
    line_msgs.reserve(max_lines);

    if (fill_lines)
    {
      // Fill blank lines to keep maximum heigth at start
      for (size_t i = 0; i < max_lines; i++)
      {
        line_msgs.push_back({string_converter.str(), Color::Default});
      }
    }

    this->enable_flex = enable_flex;
  };

  inline auto element()
  {

    for (auto &el : line_msgs)
    {
      line_elements.push_back(text_element(el.msg, el.msg_color));
    }

    if (enable_flex)
      return hbox(
          window(text(name), vbox(std::move(line_elements)) | flex));
    else
      return hbox(
          window(text(name), vbox(std::move(line_elements))));
  }

  template <class... T>
  inline void LOG(T &&... args)
  {
    add_msg(Color::Default, args...);
  }

  template <class... T>
  inline void LOGC(T &&... args)
  {
    add_msg(Color::Cyan, args...);
  }

  template <class... T>
  inline void LOGR(T &&... args)
  {
    add_msg(Color::Red, args...);
  }

  template <class... T>
  inline void LOGG(T &&... args)
  {
    add_msg(Color::Green, args...);
  }

  template <class... T>
  inline void LOGY(T &&... args)
  {
    add_msg(Color::Yellow, args...);
  }

  template <class... T>
  inline void LOGM(T &&... args)
  {
    add_msg(Color::Magenta, args...);
  }
};

WindowLogger test1(" Information ", 5, true, true);
WindowLogger test2(" Events ", 5, true, true);

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

  thread t([&]() {
    std::string reset_position;
    uint8_t i = 0;
    while (true)
    {
      auto document = window(text(L" Real Time Logs "),
                             vbox(hbox(test1.element(), test2.element()),
                                  window(hbox(text(L" "), spinner(15, i) | bold, text(L" Summary ")), text(L" Running ") | color(Color::Green))));
      auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
      Render(screen, document.get());
      std::cout << reset_position << screen.ToString() << std::flush;
      reset_position = screen.ResetPosition();
      std::this_thread::sleep_for(0.03s);
      i += 1 % 10;
    }
  });

  for (;;)
  {

    test1.LOG("Value: ", rand());
    test1.LOGG("Value: ", rand());
    test2.LOGM("[", __FILE__, "] ", rand());
    test2.LOGY("[", __FILE__, "] ", rand());
    std::this_thread::sleep_for(0.1s);
  }
  std::cout << std::endl;
}

@ArthurSonzogni
Copy link
Owner

That's beautiful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants