App Basic Structure
Before we proceed any further, we are going to refactor the code we already have to make it easier
to scale up in the future. We are going to move the event loop into a method on the App struct.
App
Create a new file ./src/app.rs:
pub struct App {    quit: bool,    frame_count: usize,    last_key_event: Option<crossterm::event::KeyEvent>,}Define some helper functions for initializing the App:
impl App {    pub fn new() -> Self {        let quit = false;        let frame_count = 0;        let last_key_event = None;        Self {            quit,            frame_count,            last_key_event,        }    }}
impl Default for App {    fn default() -> Self {        Self::new()    }}App methods
App::run
Now define a run method for App:
impl App {    pub async fn run(        &mut self,        mut tui: Tui,        mut events: Events,    ) -> Result<()> {        loop {            if let Some(e) = events.next().await {                self.handle_event(e, &mut tui)?            }            if self.should_quit() {                break;            }        }        Ok(())    }}App::quit and App::should_quit
The run method uses a should_quit method (and a corresponding quit method) that you can define
like this:
impl App {    fn should_quit(&self) -> bool {        self.quit    }
    fn quit(&mut self) {        self.quit = true    }}App::handle_event
This run method also uses a handle_event method that you can define like so:
impl App {    fn handle_event(&mut self, e: Event, tui: &mut Tui) -> Result<()> {        use crossterm::event::Event as CrosstermEvent;        use crossterm::event::KeyCode;        match e {            Event::Crossterm(CrosstermEvent::Key(key)) => {                self.last_key_event = Some(key);                if key.code == KeyCode::Esc {                    self.quit()                }            }            Event::Render => self.draw(tui)?,            _ => (),        };        Ok(())    }}App::draw
Finally, for the draw method, you could define it like this:
use ratatui::widgets::*;
impl App {    fn draw(&mut self, tui: &mut Tui) -> Result<()> {        tui.draw(|frame| {            frame.render_widget(                Paragraph::new(format!(                    "frame counter: {}",                    frame.count()                )),                frame.size(),            );        })?;        Ok(())    }}But let’s go one step further and set ourselves up for using the StatefulWidget pattern.
StatefulWidget pattern
Define the draw method like this:
impl App {    fn draw(&mut self, tui: &mut Tui) -> Result<()> {        tui.draw(|frame| {            self.frame_count = frame.count();            frame.render_stateful_widget(AppWidget, frame.size(), self);        })?;        Ok(())    }}This uses a unit-like struct called AppWidget that can be rendered as a StatefulWidget using
the App struct as its state.
use ratatui::widgets::{StatefulWidget, Paragraph};
struct AppWidget;
impl StatefulWidget for AppWidget {    type State = App;
    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {        Paragraph::new(format!("frame counter: {}", state.frame_count))            .render(area, buf);
        if let Some(key) = state.last_key_event {            Paragraph::new(format!("last key event: {:?}", key.code))                .right_aligned()                .render(area, buf);        }    }}Here’s the full ./src/app.rs file for your reference:
src/app.rs (click to expand) 
use color_eyre::eyre::Result;use ratatui::prelude::*;use ratatui::widgets::*;
use crate::{    events::{Event, Events},    tui::Tui};
pub struct App {    quit: bool,    frame_count: usize,    last_key_event: Option<crossterm::event::KeyEvent>,}
impl App {    pub fn new() -> Self {        let quit = false;        let frame_count = 0;        let last_key_event = None;        Self {            quit,            frame_count,            last_key_event,        }    }
    pub async fn run(        &mut self,        mut tui: Tui,        mut events: Events,    ) -> Result<()> {        loop {            if let Some(e) = events.next().await {                self.handle_event(e, &mut tui)?            }            if self.should_quit() {                break;            }        }        Ok(())    }
    fn handle_event(&mut self, e: Event, tui: &mut Tui) -> Result<()> {        use crossterm::event::Event as CrosstermEvent;        use crossterm::event::KeyCode;        match e {            Event::Crossterm(CrosstermEvent::Key(key)) => {                self.last_key_event = Some(key);                if key.code == KeyCode::Esc {                    self.quit()                }            }            Event::Render => self.draw(tui)?,            _ => (),        };        Ok(())    }
    fn draw(&mut self, tui: &mut Tui) -> Result<()> {        tui.draw(|frame| {            self.frame_count = frame.count();            frame.render_stateful_widget(AppWidget, frame.size(), self);        })?;        Ok(())    }
    fn should_quit(&self) -> bool {        self.quit    }
    fn quit(&mut self) {        self.quit = true    }}
impl Default for App {    fn default() -> Self {        Self::new()    }}
struct AppWidget;
impl StatefulWidget for AppWidget {    type State = App;
    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {        Paragraph::new(format!("frame counter: {}", state.frame_count))            .render(area, buf);
        if let Some(key) = state.last_key_event {            Paragraph::new(format!("last key event: {:?}", key.code))                .right_aligned()                .render(area, buf);        }    }}Conclusion
Now, run your application with a modified main.rs that uses the App struct you just created:
pub mod app;pub mod errors;pub mod events;pub mod tui;pub mod widgets;
#[tokio::main]async fn main() -> color_eyre::Result<()> {    errors::install_hooks()?;
    let tui = tui::init()?;    let events = events::Events::new();    app::App::new().run(tui, events).await?;    tui::restore()?;
    Ok(())}You should get the same results as before.

Your file structure should now look like this:
.├── Cargo.lock├── Cargo.toml└── src   ├── app.rs   ├── crates_io_api_helper.rs   ├── errors.rs   ├── events.rs   ├── main.rs   └── tui.rs