The Event Loop
So far, our app isn't really "doing" anything – in fact, it won't even respond if we try to close it with the "x" button. To handle input from the user, we must use the event loop.
winit delivers each event to our App by calling a method of ApplicationHandler. Events that concern a window go to window_event; let's print them:
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
// [...] create the window, renderer and canvas, as before
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
println!("{:?}", event);
}
}
event_loop.run_app calls the application's methods for each new event, until the program exits.
window_event is the one we are most interested in. It receives:
- A
WindowId– but since we only have 1 window in this example, the ID will always match with our window's ID. - A
WindowEventenum, which contains information about the window event.
You can run the example to see the different types of WindowEvents being printed as you interact with the window! You might see something like:
CursorMoved { device_id: DeviceId(X(DeviceId(2))), position: PhysicalPosition { x: 931.1624145507813, y: 270.08074951171875 } }
CursorMoved { device_id: DeviceId(X(DeviceId(2))), position: PhysicalPosition { x: 981.8682861328125, y: 257.404296875 } }
CursorLeft { device_id: DeviceId(X(DeviceId(2))) }
ModifiersChanged(Modifiers { state: ModifiersState(0x0), pressed_mods: ModifiersKeyState(0x0) })
Focused(false)
Exiting on Close
Clicking on the window's close button also creates a WindowEvent. We'll capture this event to exit the application when the user requests it:
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
_ => {}
}
}
Try running it! The program should exit when you click the close button.
ActiveEventLoop::exitasks the event loop to stop;run_appthen returns.
Tracking Mouse Movement
Using the CursorMoved event, we can track the mouse position. Let's keep the latest position we know in the app state, and update it whenever we get a CursorMoved event:
struct AppState {
// [...] context, surface, window, canvas
mouse_position: PhysicalPosition<f64>,
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let Some(state) = self.state.as_mut() else {
return;
};
match event {
WindowEvent::CursorMoved { position, .. } => {
state.mouse_position = position;
}
// ...
}
}
Re-rendering
So far, our code only renders the window once – after that, there's no rendering, only event handling. But suppose we wanted the red square to follow the mouse. How can we re-render the square in the new position?
We'll need to re-render every time the mouse position changes. The correct way to do this is to request_redraw, so that the platform knows we want to draw some new stuff. Then, we'll receive a WindowEvent::RedrawRequested event, and that's when we can render a new frame:
match event {
WindowEvent::CursorMoved { position, .. } => {
state.mouse_position = position;
state.window.request_redraw();
}
WindowEvent::RedrawRequested => {
render(&state.context, &state.surface, &state.window, &mut state.canvas, state.mouse_position);
}
// ...
}
Finally, we should update our render function to take the mouse position into account – we can just use those coordinates for the square position:
fn render<T: Renderer>(
context: &PossiblyCurrentContext,
surface: &Surface<WindowSurface>,
window: &Window,
canvas: &mut Canvas<T>,
square_position: PhysicalPosition<f64>,
) {
//...
canvas.clear_rect(
square_position.x as u32,
square_position.y as u32,
30,
30,
Color::rgbf(1., 0., 0.),
);
//...
}
Our code runs! There's just one small problem...

What happened? Since there's no code to "erase" the old square, all the squares pile up on each other, creating a red mess. To fix this, we should clear the entire window before rendering anything new.
canvas.clear_rect(0, 0, size.width, size.height, Color::black());
Now, if you run the code, the red square will happily follow the cursor wherever it goes.

To recap, here's the code we've written:
use std::num::NonZeroU32;
use femtovg::renderer::OpenGl;
use femtovg::{Canvas, Color};
use glutin::surface::Surface;
use glutin::{context::PossiblyCurrentContext, display::Display};
use glutin_winit::DisplayBuilder;
use raw_window_handle::HasWindowHandle;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalPosition;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::{dpi::PhysicalSize, window::Window};
use glutin::{
config::ConfigTemplateBuilder,
context::ContextAttributesBuilder,
display::GetGlDisplay,
prelude::*,
surface::{SurfaceAttributesBuilder, WindowSurface},
};
struct App {
state: Option<AppState>,
}
struct AppState {
context: PossiblyCurrentContext,
surface: Surface<WindowSurface>,
window: Window,
canvas: Canvas<OpenGl>,
mouse_position: PhysicalPosition<f64>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_some() {
return;
}
let (context, gl_display, window, surface) = create_window(event_loop);
let renderer = unsafe { OpenGl::new_from_function_cstr(|s| gl_display.get_proc_address(s).cast()) }
.expect("Cannot create renderer");
let mut canvas = Canvas::new(renderer).expect("Cannot create canvas");
canvas.set_size(1000, 600, window.scale_factor() as f32);
self.state = Some(AppState {
context,
surface,
window,
canvas,
mouse_position: PhysicalPosition::new(0., 0.),
});
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: winit::window::WindowId, event: WindowEvent) {
let Some(state) = self.state.as_mut() else {
return;
};
match event {
WindowEvent::CursorMoved { position, .. } => {
state.mouse_position = position;
state.window.request_redraw();
}
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
render(
&state.context,
&state.surface,
&state.window,
&mut state.canvas,
state.mouse_position,
);
}
_ => {}
}
}
}
fn main() {
let event_loop = EventLoop::new().unwrap();
let mut app = App { state: None };
event_loop.run_app(&mut app).unwrap();
}
fn create_window(event_loop: &ActiveEventLoop) -> (PossiblyCurrentContext, Display, Window, Surface<WindowSurface>) {
let window_attrs = Window::default_attributes()
.with_inner_size(PhysicalSize::new(1000., 600.))
.with_title("Femtovg");
let template = ConfigTemplateBuilder::new().with_alpha_size(8);
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attrs));
let (window, gl_config) = display_builder
.build(event_loop, template, |mut configs| configs.next().unwrap())
.unwrap();
let window = window.unwrap();
let gl_display = gl_config.display();
let raw_window_handle = window.window_handle().unwrap().as_raw();
let context_attributes = ContextAttributesBuilder::new().build(Some(raw_window_handle));
let mut not_current_gl_context =
Some(unsafe { gl_display.create_context(&gl_config, &context_attributes).unwrap() });
let raw_window_handle = window.window_handle().unwrap().as_raw();
let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
raw_window_handle,
NonZeroU32::new(1000).unwrap(),
NonZeroU32::new(600).unwrap(),
);
let surface = unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };
(
not_current_gl_context.take().unwrap().make_current(&surface).unwrap(),
gl_display,
window,
surface,
)
}
fn render(
context: &PossiblyCurrentContext,
surface: &Surface<WindowSurface>,
window: &Window,
canvas: &mut Canvas<OpenGl>,
square_position: PhysicalPosition<f64>,
) {
// Make sure the canvas has the right size:
let size = window.inner_size();
canvas.set_size(size.width, size.height, window.scale_factor() as f32);
canvas.clear_rect(0, 0, size.width, size.height, Color::black());
// Make smol red rectangle
canvas.clear_rect(
square_position.x as u32,
square_position.y as u32,
30,
30,
Color::rgbf(1., 0., 0.),
);
// Tell renderer to execute all drawing commands
canvas.flush();
// Display what we've just rendered
surface.swap_buffers(context).expect("Could not swap buffers");
}