veeso / tui-realm

👑 tui-rs framework to build stateful applications with a React/Elm inspired approach
MIT License
558 stars 25 forks source link

Is it possible to trigger an event based on another event? #45

Closed LeonardoDeFaveri closed 1 year ago

LeonardoDeFaveri commented 1 year ago

I'm writing a music player with a TUI. I've been able to open a help window when Ctrl+H is pressed and a window showing a playlist songs when Enter is pressed over a table row showing all playlist names.

The following images shows what I mean: image image

In both cases, those window are closed when ESC is pressed, in every place. Now the problem is that the status bar at the bottom should show a different messages in different situations. For instance:

Right now, the status bar has a subscription to every event and chooses the message based on the type of event it receives. However, I can't use an existing event to detect when playlist window is opened. I mean, I could detect when Enter is pressed, but I'm planning to use the same key for other things, so I'd move the problem to distinguish between different ENTER-event sources.

I thought about using SubClause::IsMounted to trigger the ENTER-event only when playlist window is mounter. However, I've divided the UI in three main sections that, from top to bottom, are: SearchBar, AppWindow, StatusBar. AppWindow is a container holding three sections: table of playlist names, main windows space, table of queued songs.

My last resort was to implement a UserEvent and trigger something like UserEvent::PlaylistWindowOpen when ENTER is pressed over a playlists table row. Unfortunately, the documentation about UserEvents make it seems like it's impossible to trigger an event based on other events, because the pool method is indipendent from other components.

So, my question is whether or not there's a way to trigger a UserEvent when another event is being handled.

veeso commented 1 year ago

So, my question is whether or not there's a way to trigger a UserEvent when another event is being handled.

Yes, you should have a Port which shares some states with your main application (like a queue) where you push a value which will make the port generate the event.

Anyway, I would use the IsMounted in subscriptions as I do in my applications. If you can't there must be a way, maybe refactoring components.

LeonardoDeFaveri commented 1 year ago

Yes, you should have a Port which shares some states with your main application (like a queue) where you push a value which will make the port generate the event.

Ok, I'll try to implement this because refactoring my components would be to much work.

LeonardoDeFaveri commented 1 year ago

Ok, I've been able to solve my problem. It was quite easy actually.

I've implemented a port which receives user events through a mpsc channel and triggers them.

pub struct UserEventPort {
    rx: std::sync::mpsc::Receiver<UserEvent>,
}

impl Poll<UserEvent> for UserEventPort {
    fn poll(&mut self) -> tuirealm::listener::ListenerResult<Option<tuirealm::Event<UserEvent>>> {
        let event = self.rx.try_recv(); // Fetches an event
        if event.is_err() {
            return Ok(None);
        }
        let event = event.unwrap();
        Ok(Some(Event::User(event))) // Triggers it
    }
}

Then, in the update method of my Model I have something like:

AppMsg::ShowHelp => {
    let _ = self.tx.send(UserEvent::HelpOpened); // Sends the user event to the port
    // Other code...
}