Back to Blog

egui Keyboard Input: Arrow Keys, Clicks & Events | Rust GUI Ep 21

Sandy LaneSandy Lane

Video: egui Keyboard Input: Arrow Keys, Clicks & Events | Rust GUI Ep 21 by Taught by Celeste AI - AI Coding Coach

Watch full page →

egui Keyboard Input: Arrow Keys, Clicks & Events | Rust GUI Ep 21

In this tutorial, you will learn how to handle keyboard and mouse input in an egui application using Rust. We build a Key Explorer app that reacts to arrow keys, WASD for movement, Tab for cycling colors, Space for resetting, and mouse clicks to place markers on a canvas.

Code

use egui::{Color32, Key, Pos2, Rect, Sense, Vec2};

const COLORS: &[Color32] = &[
  Color32::RED,
  Color32::GREEN,
  Color32::BLUE,
  Color32::YELLOW,
];

struct MyApp {
  pos: Pos2,
  color_idx: usize,
  markers: Vec<Pos2>,
}

impl Default for MyApp {
  fn default() -> Self {
    Self {
      pos: Pos2::new(100.0, 100.0),
      color_idx: 0,
      markers: Vec::new(),
    }
  }
}

impl MyApp {
  fn update(&mut self, ctx: &egui::Context) {
    // Request continuous repaint to keep input responsive
    ctx.request_repaint();

    // Read input state each frame
    let input = ctx.input(|i| i.clone());

    // Move position with arrow keys or WASD
    let delta = 5.0;
    if input.key_pressed(Key::ArrowUp) || input.key_pressed(Key::W) {
      self.pos.y -= delta;
    }
    if input.key_pressed(Key::ArrowDown) || input.key_pressed(Key::S) {
      self.pos.y += delta;
    }
    if input.key_pressed(Key::ArrowLeft) || input.key_pressed(Key::A) {
      self.pos.x -= delta;
    }
    if input.key_pressed(Key::ArrowRight) || input.key_pressed(Key::D) {
      self.pos.x += delta;
    }

    // Cycle colors with Tab key
    if input.key_pressed(Key::Tab) {
      self.color_idx = (self.color_idx + 1) % COLORS.len();
    }

    // Reset position and markers with Space key
    if input.key_pressed(Key::Space) {
      self.pos = Pos2::new(100.0, 100.0);
      self.markers.clear();
    }

    // Create a clickable canvas area
    egui::CentralPanel::default().show(ctx, |ui| {
      let (rect, response) = ui.allocate_exact_size(Vec2::new(300.0, 300.0), Sense::click());

      // On mouse click inside canvas, add a marker
      if response.clicked() {
        if let Some(click_pos) = response.interact_pointer_pos() {
          // Clamp click position inside canvas rect
          let pos = Pos2::new(
            click_pos.x.clamp(rect.left(), rect.right()),
            click_pos.y.clamp(rect.top(), rect.bottom()),
          );
          self.markers.push(pos);
        }
      }

      // Paint markers as filled circles
      let painter = ui.painter();
      for &marker_pos in &self.markers {
        painter.circle_filled(marker_pos, 5.0, COLORS[self.color_idx]);
      }

      // Paint a movable square cursor at current position
      let cursor_rect = Rect::from_center_size(self.pos, Vec2::splat(20.0));
      painter.rect_stroke(cursor_rect, 0.0, (2.0, COLORS[self.color_idx]));
    });
  }
}

Key Points

  • Use ctx.input(|i| i.key_pressed(Key::...)) to detect single key presses like arrow keys and WASD.
  • Cycle through colors or reset state by responding to Tab and Space keys respectively.
  • Call ctx.request_repaint() to keep the UI updating and responsive to input events.
  • Use Sense::click() on UI elements to detect mouse clicks and response.interact_pointer_pos() to get click coordinates.
  • Draw interactive markers and a movable cursor on a canvas using egui's painter API with circle_filled and rect_stroke.