Overview
Events in Freya allow your components to respond to user interactions like mouse clicks, keyboard input, hovering, and more. Event handlers are functions attached to elements that run when specific events occur.Event handlers in Freya are similar to event listeners in HTML/JavaScript - they let you make your UI interactive.
Basic Event Handling
Attach event handlers using.on_* methods:
use freya::prelude::*;
fn app() -> impl IntoElement {
rect()
.on_mouse_up(|_| {
println!("Clicked!");
})
.child("Click me")
}
Mouse Events
Click Events
- on_mouse_up
- on_mouse_down
- on_press
Fires when a mouse button is released:
rect()
.on_mouse_up(|_| {
println!("Mouse button released");
})
.child("Click me")
Fires when a mouse button is pressed:
rect()
.on_mouse_down(|_| {
println!("Mouse button pressed");
})
.child("Press me")
Convenient alternative to
on_mouse_up:rect()
.on_press(|_| {
println!("Clicked!");
})
.child("Click me")
Hover Events
use freya::prelude::*;
#[derive(PartialEq)]
struct HoverableBox;
impl Component for HoverableBox {
fn render(&self) -> impl IntoElement {
let mut hover = use_state(|| false);
rect()
.background(if *hover.read() {
(100, 100, 255)
} else {
(200, 200, 200)
})
.on_mouse_over(move |_| hover.set(true))
.on_mouse_leave(move |_| hover.set(false))
.padding(Gaps::new_all(20.))
.child("Hover over me")
}
}
Mouse Movement
Track mouse position:use freya::prelude::*;
#[derive(PartialEq)]
struct MouseTracker;
impl Component for MouseTracker {
fn render(&self) -> impl IntoElement {
let mut pos = use_state(|| (0.0, 0.0));
rect()
.width(Size::fill())
.height(Size::fill())
.on_mouse_move(move |e: Event<MouseEventData>| {
pos.set((
e.element_location.x,
e.element_location.y
));
})
.child(format!("Mouse: {:.0}, {:.0}", pos.read().0, pos.read().1))
}
}
Mouse Buttons
Detect which button was clicked:use freya::prelude::*;
fn app() -> impl IntoElement {
rect()
.on_mouse_up(|e: Event<MouseEventData>| {
match e.button {
Some(MouseButton::Left) => println!("Left click"),
Some(MouseButton::Right) => println!("Right click"),
Some(MouseButton::Middle) => println!("Middle click"),
_ => {}
}
})
.child("Click with any button")
}
Keyboard Events
Key Press
use freya::prelude::*;
#[derive(PartialEq)]
struct KeyListener;
impl Component for KeyListener {
fn render(&self) -> impl IntoElement {
let mut text = use_state(|| String::new());
rect()
.focusable(true)
.on_key_down(move |e: Event<KeyboardEventData>| {
if let Some(character) = e.try_as_str() {
text.write().push_str(character);
}
})
.child(text.read().clone())
}
}
Keyboard Shortcuts
Handle specific keys and modifiers:use freya::prelude::*;
use keyboard_types::{Code, Key, Modifiers};
#[derive(PartialEq)]
struct ShortcutHandler;
impl Component for ShortcutHandler {
fn render(&self) -> impl IntoElement {
rect()
.focusable(true)
.on_key_down(move |e: Event<KeyboardEventData>| {
// Check for Ctrl+S
if e.modifiers.contains(Modifiers::CONTROL)
&& e.key == Key::Character("s".into()) {
println!("Save shortcut!");
e.prevent_default();
}
// Check for Escape
if e.key == Key::Escape {
println!("Escape pressed!");
}
// Check for Enter
if e.code == Code::Enter {
println!("Enter pressed!");
}
})
.child("Press Ctrl+S or Escape")
}
}
Scroll Events
Mouse Wheel
use freya::prelude::*;
#[derive(PartialEq)]
struct ScrollTracker;
impl Component for ScrollTracker {
fn render(&self) -> impl IntoElement {
let mut scroll = use_state(|| 0.0);
rect()
.height(Size::px(200.))
.on_wheel(move |e: Event<WheelEventData>| {
*scroll.write() += e.delta_y;
})
.child(format!("Scroll: {:.0}", scroll.read()))
}
}
Focus Events
Handle focus changes:use freya::prelude::*;
#[derive(PartialEq)]
struct FocusableInput;
impl Component for FocusableInput {
fn render(&self) -> impl IntoElement {
let mut focused = use_state(|| false);
rect()
.focusable(true)
.background(if *focused.read() {
(200, 200, 255)
} else {
(240, 240, 240)
})
.border(Border::all((
2.,
if *focused.read() {
(100, 100, 255, 255)
} else {
(200, 200, 200, 255)
}
)))
.on_focus(move |_| focused.set(true))
.on_blur(move |_| focused.set(false))
.padding(Gaps::new_all(12.))
.child("Click to focus")
}
}
Event Object
All event handlers receive anEvent<T> object:
use freya::prelude::*;
rect()
.on_mouse_up(|e: Event<MouseEventData>| {
// Access event data
let x = e.global_location.x;
let y = e.element_location.y;
let button = e.button;
// Control event behavior
e.stop_propagation();
e.prevent_default();
})
Event Data Types
MouseEventData
pub struct MouseEventData {
pub global_location: CursorPoint,
pub element_location: CursorPoint,
pub button: Option<MouseButton>,
}
KeyboardEventData
pub struct KeyboardEventData {
pub key: Key,
pub code: Code,
pub modifiers: Modifiers,
}
WheelEventData
pub struct WheelEventData {
pub delta_x: f64,
pub delta_y: f64,
}
SizedEventData
pub struct SizedEventData {
pub area: Area,
pub visible_area: Area,
pub inner_sizes: Size2D,
}
Event Propagation
By default, events propagate up the component tree. You can stop this:use freya::prelude::*;
fn app() -> impl IntoElement {
rect()
.on_press(|_| println!("Parent clicked"))
.child(
rect()
.on_press(|e: Event<PressEventData>| {
e.stop_propagation(); // Prevent parent handler
println!("Child clicked");
})
.child("Click me")
)
}
Propagation Example
use freya::prelude::*;
fn app() -> impl IntoElement {
rect()
.width(Size::px(200.))
.height(Size::px(200.))
.background(Color::RED)
.on_press(|_| println!("Outer clicked"))
.child(
rect()
.width(Size::px(150.))
.height(Size::px(150.))
.background(Color::BLUE)
.on_press(|_| println!("Middle clicked"))
.child(
rect()
.width(Size::px(100.))
.height(Size::px(100.))
.background(Color::GREEN)
.on_press(|e: Event<PressEventData>| {
e.stop_propagation();
println!("Inner clicked")
})
)
)
}
// Clicking green box prints: "Inner clicked"
// Clicking blue box prints: "Middle clicked", "Outer clicked"
// Clicking red box prints: "Outer clicked"
File Drop Events
Handle drag-and-drop files:use freya::prelude::*;
#[derive(PartialEq)]
struct FileDropZone;
impl Component for FileDropZone {
fn render(&self) -> impl IntoElement {
let mut files = use_state(|| Vec::new());
rect()
.width(Size::px(300.))
.height(Size::px(200.))
.background((240, 240, 240))
.border(Border::all((2., (200, 200, 200, 255))))
.on_file_drop(move |e: Event<FileDropEventData>| {
files.write().extend(e.files.clone());
})
.center()
.child(
if files.read().is_empty() {
"Drop files here"
} else {
&format!("{} files dropped", files.read().len())
}
)
}
}
Resize Events
React to element size changes:use freya::prelude::*;
#[derive(PartialEq)]
struct ResizeTracker;
impl Component for ResizeTracker {
fn render(&self) -> impl IntoElement {
let mut size = use_state(|| (0.0, 0.0));
rect()
.width(Size::fill())
.height(Size::fill())
.on_sized(move |e: Event<SizedEventData>| {
size.set((
e.visible_area.width(),
e.visible_area.height()
));
})
.child(format!(
"Size: {:.0}x{:.0}",
size.read().0,
size.read().1
))
}
}
Complete Interactive Example
Here’s a complete example combining multiple event types:use freya::prelude::*;
fn main() {
launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}
fn app() -> impl IntoElement {
rect()
.expanded()
.center()
.child(InteractiveBox {})
}
#[derive(PartialEq)]
struct InteractiveBox;
impl Component for InteractiveBox {
fn render(&self) -> impl IntoElement {
let mut hover = use_state(|| false);
let mut pressed = use_state(|| false);
let mut clicks = use_state(|| 0);
let mut pos = use_state(|| (0.0, 0.0));
rect()
.width(Size::px(200.))
.height(Size::px(200.))
.background(
if *pressed.read() {
(255, 100, 100)
} else if *hover.read() {
(100, 100, 255)
} else {
(200, 200, 200)
}
)
.corner_radius(CornerRadius::new(12.))
.center()
.focusable(true)
// Mouse events
.on_mouse_over(move |_| hover.set(true))
.on_mouse_leave(move |_| hover.set(false))
.on_mouse_down(move |_| pressed.set(true))
.on_mouse_up(move |_| {
pressed.set(false);
*clicks.write() += 1;
})
.on_mouse_move(move |e: Event<MouseEventData>| {
pos.set((
e.element_location.x,
e.element_location.y
));
})
// Keyboard events
.on_key_down(move |e: Event<KeyboardEventData>| {
if e.key == keyboard_types::Key::Character("r".into()) {
clicks.set(0);
}
})
.child(
rect()
.child(format!("Clicks: {}", clicks.read()))
.child(format!(
"Mouse: {:.0}, {:.0}",
pos.read().0,
pos.read().1
))
.child("Press 'r' to reset")
)
}
}
Best Practices
Use move closures for state
Use move closures for state
Always use
move when capturing state in event handlers:let mut count = use_state(|| 0);
rect().on_mouse_up(move |_| { // ✅ move keyword
*count.write() += 1;
})
Prevent default when needed
Prevent default when needed
Stop browser-like behaviors:
rect()
.on_key_down(move |e: Event<KeyboardEventData>| {
if e.key == Key::Tab {
e.prevent_default(); // Don't change focus
}
})
Make interactive elements focusable
Make interactive elements focusable
Elements that handle keyboard events should be focusable:
rect()
.focusable(true) // ✅ Required for keyboard events
.on_key_down(|e| { /* ... */ })
Batch state updates
Batch state updates
Multiple state updates in one handler are efficient:
rect().on_mouse_up(move |_| {
*count.write() += 1;
name.set("Updated".to_string());
// Both updates happen together
})
Common Patterns
Double Click
use freya::prelude::*;
use std::time::Instant;
#[derive(PartialEq)]
struct DoubleClickDetector;
impl Component for DoubleClickDetector {
fn render(&self) -> impl IntoElement {
let mut last_click = use_state(|| None::<Instant>);
rect()
.on_mouse_up(move |_| {
let now = Instant::now();
if let Some(last) = *last_click.read() {
if now.duration_since(last).as_millis() < 300 {
println!("Double click!");
last_click.set(None);
return;
}
}
last_click.set(Some(now));
})
.child("Double click me")
}
}
Long Press
use freya::prelude::*;
#[derive(PartialEq)]
struct LongPressButton;
impl Component for LongPressButton {
fn render(&self) -> impl IntoElement {
let mut pressing = use_state(|| false);
use_effect(move || {
if *pressing.read() {
// Start timer
let handle = spawn_task(move || async move {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
if *pressing.read() {
println!("Long press!");
}
});
}
});
rect()
.on_mouse_down(move |_| pressing.set(true))
.on_mouse_up(move |_| pressing.set(false))
.on_mouse_leave(move |_| pressing.set(false))
.child("Press and hold")
}
}
Next Steps
Components
Build interactive components
State Management
Manage component state
Hooks
Use lifecycle hooks
Built-in Components
Explore pre-built interactive components