Skip to content
New issue

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

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

Already on GitHub? # to your account

Catching events for nested components #898

Open
MarcelFerrari opened this issue Jul 16, 2024 · 2 comments
Open

Catching events for nested components #898

MarcelFerrari opened this issue Jul 16, 2024 · 2 comments

Comments

@MarcelFerrari
Copy link

MarcelFerrari commented Jul 16, 2024

Hi everyone,

I am trying to understand how the CatchEvent decorator interacts with components and with the Render() method.

I am writing a program that has multiple tabs containing lots of information and I have split each tab into its own class. Each class has a construct() method which returns a Component object containing all the data representing that tab. This component may contain CatchEvent decorators that are only relevant for that specific tab. However, it seems like events are not forwarded to child components whenever the Render() method is called.

I include a small example to reproduce the issue.

This code works absolutely fine

// FTXUI includes
#include <ftxui/component/captured_mouse.hpp>
#include <ftxui/component/component.hpp>
#include <ftxui/component/component_base.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>

using namespace ftxui;

class ExampleUI
{
public:
    Component render_example_tab()
    {
        Component rax = Renderer([&] {
        return text("N recorded events: " + std::to_string(n_events));
        });

        rax |= CatchEvent([&](Event event) {
            if (event == Event::ArrowDown) {
                n_events++;
                return true;
            } else if (event == Event::ArrowUp) {
                n_events--;
                return true;
            }
            return false; });
        
        return std::move(rax);
    }

    private:
        int n_events = 0;
};

int main()
{
    // Init UI class
    ExampleUI example_ui;
    
    auto screen = ScreenInteractive::Fullscreen();

    Component display = example_ui.render_example_tab();

    screen.Loop(display);
    return 0;
}

This one however fails to register the catch event calls:

// FTXUI includes
#include <ftxui/component/captured_mouse.hpp>
#include <ftxui/component/component.hpp>
#include <ftxui/component/component_base.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>

using namespace ftxui;

class ExampleUI
{
public:
    Component render_example_tab()
    {
        Component rax = Renderer([&] {
        return text("N recorded events: " + std::to_string(n_events));
        });

        rax |= CatchEvent([&](Event event) {
            if (event == Event::ArrowDown) {
                n_events++;
                return true;
            } else if (event == Event::ArrowUp) {
                n_events--;
                return true;
            }
            return false; });
        
        return std::move(rax);
    }

    private:
        int n_events = 0;
};

int main()
{
    // Init UI class
    ExampleUI example_ui;
    
    auto screen = ScreenInteractive::Fullscreen();

    Component display = example_ui.render_example_tab();

    Component frame = Renderer([&]{
        return window(text("Example UI"), display->Render());
    });

    screen.Loop(frame);
    return 0;
}

From what I understand calling the Render() method transforms components into "static" elements before any events are caught and handled.

How can one go about achieving the functionality I want?

Many thanks in advance,

Marcel

@ArthurSonzogni
Copy link
Owner

ArthurSonzogni commented Jul 17, 2024

Each component defines how it routes events.
Typically, most implementations are:

  • Forwarding keyboard events to the currently focused child.
  • Forwarding mouse events to every child.

In your case, the problem was using:

    Component frame = Renderer([&]{
        return window(text("Example UI"), display->Render());
    });

instead of:

    Component frame = Renderer(display, [&]{
        return window(text("Example UI"), display->Render());
    });

So, you create a brand new Component, without forwarding events to display.
The second form does "decorate" the wrapped component, by overriding only its "Render()" function, but still forwarding everything else.

@MarcelFerrari
Copy link
Author

Thank you for the prompt reply!
I understand now. I looked up the documentation and found the Component Renderer(Component child, std::function<Element()> render) function. In the case of multiple children, e.g. when rendering multiple components into a vbox, I assume it is possible to wrap all the child components into e.g. a stacked container in order to propagate events, even if the container is not rendered itself, right?

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

No branches or pull requests

2 participants