Ratatui detect arrow key press and release

⚓ Rust    📅 2026-04-22    👤 surdeus    👁️ 2      

surdeus

I am new to ratatui and want to implement a console-based controller for a USB nerf missile launcher. I can capture the key press events of the arrow key, but releasing the key is not being detected as an event. I.e. only ever the key press event fires.

//! TUI controller for the USB missile launcher.

use std::io;

use crossterm::event;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
use log::{debug, error, info};
use ratatui::prelude::*;
use ratatui::symbols::border;
use ratatui::widgets::{Block, Paragraph};
use ratatui::{DefaultTerminal, Frame};

fn main() -> io::Result<()> {
    env_logger::init();
    ratatui::run(|terminal| App::new().run(terminal))
}

#[derive(Debug)]
struct App {
    exit: bool,
}

impl App {
    /// Crate a new application.
    #[must_use]
    pub const fn new() -> Self {
        Self { exit: false }
    }

    /// Run the application's main loop until the user quits.
    ///
    /// # Errors
    ///
    /// Returns an [`io::Error`] if any I/O error occurs.
    pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
        while !self.exit {
            terminal.draw(|frame| self.draw(frame))?;
            self.handle_events()?;
        }
        Ok(())
    }

    fn draw(&self, frame: &mut Frame<'_>) {
        frame.render_widget(self, frame.area());
    }

    fn handle_events(&mut self) -> io::Result<()> {
        if let Event::Key(key_event) = event::read()? {
            self.handle_key_event(key_event);
        };

        Ok(())
    }

    const fn exit(&mut self) {
        self.exit = true;
    }

    fn handle_key_event(&mut self, key_event: KeyEvent) {
        info!("Key event: {:?}", key_event);
        match key_event.kind {
            KeyEventKind::Press => match key_event.code {
                KeyCode::Esc => self.exit(),
                KeyCode::Left | KeyCode::Right | KeyCode::Up | KeyCode::Down | KeyCode::Enter => {
                    println!("KEY PRESSED: {key_event:?}")
                }
                other => debug!("Unsupported key pressed: {other:?}"),
            },
            KeyEventKind::Release => match key_event.code {
                KeyCode::Left | KeyCode::Right | KeyCode::Up | KeyCode::Down | KeyCode::Enter => {
                    println!("KEY RELEASED: {key_event:?}");
                }
                other => debug!("Unsupported key released: {other:?}"),
            },
            KeyEventKind::Repeat => debug!("Unsupported key repeat: {key_event:?}"),
        }
    }
}

impl Widget for &App {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let title = Line::from(" Counter App Tutorial ".bold());
        let instructions = Line::from(vec![
            " Decrement ".into(),
            "<Left>".blue().bold(),
            " Increment ".into(),
            "<Right>".blue().bold(),
            " Quit ".into(),
            "<Q> ".blue().bold(),
        ]);
        let block = Block::bordered()
            .title(title.centered())
            .title_bottom(instructions.centered())
            .border_set(border::THICK);

        Paragraph::new("Foobar")
            .centered()
            .block(block)
            .render(area, buf);
    }
}
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Decrement <Left> Increment <Right> Quit <Q> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛KEY PRESSED: KeyEvent { code: Up, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                                                                        KEY PRESSED: KeyEvent { code: Left, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
              KEY PRESSED: KeyEvent { code: Down, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                                                                                        KEY PRESSED: KeyEvent { code: Right, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                               KEY PRESSED: KeyEvent { code: Up, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                                                                                                       KEY PRESSED: KeyEvent { code: Left, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                             KEY PRESSED: KeyEvent { code: Down, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                                                                                                                       KEY PRESSED: KeyEvent { code: Right, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                              KEY PRESSED: KeyEvent { code: Up, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
                                                                                                                                                                      KEY PRESSED: KeyEvent { code: Left, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }

How can I differentiate between key down and key release events?

3 posts - 2 participants

Read full topic

🏷️ Rust_feed