ArthurSonzogni / FTXUI

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

Is there a way to modify a text field at runtime? #143

Closed cmorganBE closed 3 years ago

cmorganBE commented 3 years ago

I don't see any examples doing this, which leads me to believe that maybe it isn't possible currently, or maybe I'm missing how to do it. Is it possible?

cmorganBE commented 3 years ago

Alright, so I've found an example of updating the text at runtime but it involves reading from a variable outside of the scope of the renderer handler function. When doing this I noticed the screen doesn't refresh unless I move the mouse or hit a key. Is there a way to invalidate the screen so the refresh occurs? From a structural standpoint should elements have a way to notify their container when their value is altered?

ArthurSonzogni commented 3 years ago

If you want to write it in a functional way, you don't "modify" at runtime. Instead the function return the right value every time it is called.

For instance: https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/component/slider_rgb.cpp#L19 Gives: https://arthursonzogni.com/FTXUI/examples/?file=./component/slider_rgb.js

(See the text RGB=(125, 125, 125))

The ScreenInteractive::Loop() is an event loop. I cause a new frame to be renderer everytime it receives an event. You may want to react to events that aren't handled by this loop, for instance a network fetch. To do this, you can post events yourself to the loop, potentially from a different thread.

screen.PostEvent(Event::Custom)

This is what the homescreen example does: https://github.com/ArthurSonzogni/FTXUI/blob/master/examples/component/homescreen.cpp#L363 https://arthursonzogni.com/FTXUI/examples/?file=./component/homescreen.js

cmorganBE commented 3 years ago

@ArthurSonzogni sending a message was actually what I was thinking in terms of causing the screen redraw loop to run. in my case I have data coming in from an outside source and the UI is static now until I interact with it. Would you accept a PR if I created an example for 'how to cause a refresh' that shows the technique of sending a custom event from a background thread to cause the loop to re-render the screen? Assuming of course the code meets your requirements.

On the same vein, would you accept a PR showing how to compose Components? The use case I have are three columns, each of which has some interactive elements for a particular area of the system. I figured it out pretty quickly from an example that did it and some trial and error but it could be helpful to the next person since it was a big question as to how to group together Elements and Components and have the components work but also not have to put all Components into one physical group.

ArthurSonzogni commented 3 years ago

I am always happy for getting PR!

Either writting some examples/ or updating the documentation: https://arthursonzogni.com/FTXUI/doc/ would be greatly appreciated!

cmorganBE commented 3 years ago

The approach I ended up using was to rebuild the Text() entry using the variable content. In the cases when the content changed without user input a screen.PostEvent(Event::Custom); used to trigger a refresh.

IvarWithoutBones commented 2 years ago

A bit of a beginner question, but how would I do this with a variable from a different class? This is the code I have:

namespace timer {
  class Renderer {
    ...
    // Wraps a split into a renderer, with a predetermined layout.
    ftxui::Component splitRenderer(timer::Split segment) {
      auto split = ftxui::Renderer([=] (bool focused) {
        auto _split{ftxui::hbox({
          ftxui::text(segment.name),
          ftxui::filler() | ftxui::flex,
          ftxui::text(segment.pbTime)
        })};

        if (focused)
          _split = _split | ftxui::bgcolor(ftxui::Color::Grey23); // ftxui does not support the `|=` operator

        return _split;
      });

      return split;
    };
  };
}

And here is the (rather simple) timer::Split class:

namespace timer {
  class Split { public:
    // Allows for simpler type declaration, `Split foo("splitName", "splitTime");`
    Split(std::string name, std::string pbTime) : name(name), pbTime(pbTime) { }

    std::string name;
    std::string pbTime;
    bool isAhead;
  };
}

When setting a text field to a variable in the Renderer class, it gets updated at runtime just fine.

However, when updating an element in the segment object, nothing seems to happen. Redrawing manually with screen.PostEvent(Event::Custom) also does not seem to fix the issue. Does anyone know how I could fix this? I would like to update the segment object, and have these changes be reflected in the UI.

ArthurSonzogni commented 2 years ago

@IvarWithoutBones, I am not 100% sure understanding the question. My best guess is that you are passing timer::Split by value in Renderer::splitRenderer. As a result, the generated component is working on a copy. Updating the original data, do not affect to copy.