#A tiny crate to write async, multi-screen ratatui apps

13 messages · Page 1 of 1 (latest)

scenic pond
#

Hey there! I wrote this crate for myself, but when I saw I was going to need it in multiple applications I extracted it into a separate crate. It allows you to create asynchronous TUIs using ratatui and tokio, it's quite small yet but it has everything you may need to build multi-screen applications.

  • You can check the docs in docs.rs/ratapp, I wrote a nice tutorial to make it easy to get started with.
  • You can see an example (the tutorial's code, finished and in a single file) in the repository's examples
  • There's the GitHub repostory if you're curious. It's lacking READMEs ATM but I'll write some soon. The docs should be simple enough to get started with it.

Let me know if you have any suggestions!

GitHub

A tiny framework to build multi-screen async applications with ratatui. - GitHub - Nekidev/ratapp: A tiny framework to build multi-screen async applications with ratatui.

dapper goblet
#

I think the design can be improved if it makes more use of the power of traits. Let me show what I say:


#[derive(Screens)]
struct App {
  Screen1(S1),
  Screen2(S2),
}

// Your current derive-macro generates this (likely)

enum ScreenID {
  Screen1,
  Screen2,
}

impl<S> ScreenState<S> for App {
  type ID = ScreenID;

  // ... ScreenState implementation
}

// This completely close the implementation and is not _possible_ to change anything. Example: Add a log middleware
// - it is possible to implement trait and enum by myself, but then the crate is non-sense

// === What I propose

// The macro should generate kinda the same code, but in other trait and _naming-safe_

// Enum has fixed name, that may collide, should use parent name
enum AppID {
  // ... same
}

// another trait for generated propagation
impl<S> PropagateScreenState<S> for App {
  type ID = AppID;

  // ... same events of `ScreenState` but suffixed with `_propagate`
  // Example
  
  async fn on_enter_propagate(&mut self, navigator: Navigator<Self::ID>, state: &mut S) {
    match self { /* ... */ }
  }
}

// And the user should implement `ScreenState`. This would be unnecessary once `specialization` stabilizies

impl ScreenState for App {
  async fn on_enter(&mut self, navigator: Navigator<Self::ID>, state: &mut S) {
    log::trace!("User enters to: {self:?}");
    self.on_enter_propagate(navigator, state);
  }

  // all functions will be defaulted to their `_propagated` version
}

// The trait `ScreenState` will need be a supertrait of `PropagatedScreenState`

I was implementing something like this time ago for a personal project, but never released.

So, that's my idea, thank you for reading. I think this crate can be great, so I want to discuss it and I'm open to implement it if you like it.

scenic pond
#

I appreciate the comment, to start with

#

you're right in that right now it's not possible to add anything that looks like middleware, so adding something like that could be a neat idea

#

about the ScreenID fixed naming, making it dynamic is already in my plans

#

about the user implementing ScreenState, what's your opinion about having some layout/middleware API instead?

#

my idea is to make it get close to web-based navigation

dapper goblet
scenic pond
dapper goblet
scenic pond
#

I intend to stick to Rust stable