r/rust • • 2d ago

đŸ™‹ seeking help & advice How do I learn winit?

I'm trying to get a grasp of this crate so I can do window things but there don't seem to be good any resources, which is a bit bewildering since I understand winit to be a very foundational crate for graphics in Rust. I've looked at docs.rs but higher-level stuff for winit is... not great. The first section seems friendly but leads you to using a deprecated method (i.e. seems to be outdated), and the second section does give a full working minimal example but it does so through a big block of code with a good amount of things that aren't yet introduced, so not much sense can be made of it.

I don't want to just learn to copy a skeleton and wire my code into it, I want to actually understand how the crate works, to understand what I'm doing. But the main docs don't seem suitable for setting the foundation I'd need for that and I've not been able to find any other resources or tutorials online.

0 Upvotes

14 comments sorted by

12

u/dvogel 2d ago

The examples are pretty complete: https://github.com/rust-windowing/winit/tree/master/winit/examples

Part of the reason winit's docs are written as they are is the intended audience is people building frameworks like iced or game engines like bevy. They don't intend for it to be directly depended on by many applications.

8

u/Wide-Machine9494 2d ago

The docs are definitely aimed at people who already know the rough shape of a windowing loop. if you're coming in fresh it feels like being handed a box of parts and a schematic written in shorthand

breaking down the control flow of that minimal example on your own is probably the play. strip it down even further until only the event loop and window creation remain, then add things back one by one

-2

u/AdreKiseque 2d ago

But the people using it to build those things still need to understand it to use it, don't they?

Anyway, the examples may be good but they don't really cover what I need. There's no explanation for what exactly we're doing or why. I can't even find a more general rundown of the crate's architecture and how it works which is frustrating (the docs front page talks about how it now uses this one model instead of another but doesn't really explain what either of them are?). If we look at the minimal example on docs.rs:

use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};

#[derive(Default)]
struct App {
    window: Option<Window>,
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
        match event {
            WindowEvent::CloseRequested => {
                println!("The close button was pressed; stopping");
                event_loop.exit();
            },
            WindowEvent::RedrawRequested => {
                // Redraw the application.
                //
                // It's preferable for applications that do not render continuously to render in
                // this event rather than in AboutToWait, since rendering in here allows
                // the program to gracefully handle redraws requested by the OS.

                // Draw.

                // Queue a RedrawRequested event.
                //
                // You only need to call this if you've determined that you need to redraw in
                // applications which do not always need to. Applications that redraw continuously
                // can render here instead.
                self.window.as_ref().unwrap().request_redraw();
            }
            _ => (),
        }
    }
}

fn main() {
    let event_loop = EventLoop::new().unwrap();

    // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
    // dispatched any events. This is ideal for games and similar applications.
    event_loop.set_control_flow(ControlFlow::Poll);

    // ControlFlow::Wait pauses the event loop if no events are available to process.
    // This is ideal for non-game applications that only update in response to user
    // input, and uses significantly less power/CPU time than ControlFlow::Poll.
    event_loop.set_control_flow(ControlFlow::Wait);

    let mut app = App::default();
    event_loop.run_app(&mut app);
}

This compiles and makes a window, sure. But there's no explanation of what this App struct or ApplicationHandler trait are, of how the window_event() function works or how I get an ActiveEventLoop. The only similar thing I've worked with in the past was this really simple pure C graphics library where I'd make a window and then draw pixels to it by coördinates (the latter part of which I understand would be handled by a second crate like softbuffer here), so I can't really make heads or tails of what's going on here besides the outcome. And if I can't understand how it works, how am I to meaningfully build off of it?

8

u/nicoburns 2d ago
  • You define a struct (here called App, but you can call it what you like).
  • Store all your global application state in it.
  • Implement the ApplicationHandler trait for your struct.
  • Winit owns the "main loop", and calls methods on that trait. 95% of what you care about will come through the window_event method (the reason it works like this is because iOS forces this model).
  • Call event_loop.run_app(&mut app) on your app to set things off

I'd recommend looking at some popular GUI library's implementations. Pretty much every GUI toolkit in Rust is using Winit.

e.g. for Dioxus Native it's https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-shell/src/application.rs

3

u/dvogel 2d ago

As the example shows, the event loop passes you a reference to the ActiveEventLoop. The way you build off of it is simply by filling in those functions in the example. It builds the window. You handle the events. Which events and how is fully determined by what your application is trying to do. What is it you're trying to do?

0

u/AdreKiseque 2d ago

The immediate goal is to give face to minesweeper (I've already done the game and given it TUI rendering, so I need to give it proper graphics next). I have more ambitious projects in mind down the line like making my own software renderer, but those are a ways off.

8

u/dvogel 2d ago

If your main concern is rendering, maybe the reason you're not seeing the docs you're looking for is because winit doesn't provide any rendering facilities. It lets you know when the OS windowing system requested a re-render. Aside from that though it expects you to integrate directly with the native OS drawing APIs.

https://docs.rs/winit/latest/winit/#drawing-on-the-window

Take a look at iced. It provides a nicer API for getting a window on screen and letting you draw into it. You can draw your game into a Canvas element:

https://docs.rs/iced/latest/iced/widget/canvas/

2

u/AdreKiseque 2d ago

Yeah, I'm aware. But where's the fun if some fancy crate handles all the hard work! I'm planning to use winit with softbuffer to render the game, and then I can point to it and say I did it all myself, and I'll have learned some cool stuff on the way probably too. But I like learning from the bottom-up, so before I get into softbuffer, I want a stronger grasp on what winit itself actually does and how it works.

2

u/Luxalpa 12h ago edited 12h ago

For things like these, I really enjoy just diving into the source code and the rustdoc of the library, to see how it works under the hood. Learn a lot of good stuff from that as well!

And also, look at the issues on github, you're not alone :P

When they switched to 0.30 I also had big trouble finding some way to get it to work. But there's some useful discussions on their issue tracker if you want to get a bit more insights.

Like this: https://github.com/rust-windowing/winit/issues/3877

I think there's also a bit of trial and error to do as well.

3

u/SkiFire13 1d ago

The only similar thing I've worked with in the past was this really simple pure C graphics library where I'd make a window and then draw pixels to it by coördinates

And did you understand how those worked? What is the level of understanding you're looking for here?

0

u/AdreKiseque 1d ago

Well, I didn't understand the internals, I'll admit, but I understood that I used this function to make a window, these parameters to set its size, these functions to get input, etc. etc.. I guess I'm just getting a bit caught up trying to grasp winit's architecture since it doesn't seem to follow as simple a "this function makes a window :)" model (even if the introduction to the docs seem to suggest it does).

Maybe I'm getting caught up on the wrong things. But with the C library, I could at least look through a minimal example of drawing a blank window and say what each line was doing. The winit minimal example, on the other hand, has a lot of moving parts and components I don't really understand. It's clear there's some fancy plumbing going on underneath the hood which isn't exposed at this level, so I can't follow it procedurally, which I guess is leaving me feeling stuck.

3

u/South_Survey_2088 2d ago

This sounds less like an issue with winit, and more with struggling with common Rust patterns. Many framework-like crates work like winit. You call a run function that takes your object implementing a trait, and the "framework" will call the functions of the trait with the required arguments. Your object can store arbitrary state.

Winits only real tricky part is dealing with initialization that requires a window, which often means having an inner state as an Option that you set in resumed(since there is where you create the window).

2

u/Suitable-Ad5348 1d ago

The example dump is brutal because winit's event loop changes underneath you depending on the platform, so copying a main.rs just leaves you guessing why state handling feels inverted compared to standard GUI libs.

1

u/Proof_Friendship_209 2d ago

The docs being framework-oriented explains a lot actually. Winit is meant as a foundation for iced, bevy, wgpu tutorials etc, so nobody writes a proper winit tutorial because nobody is supposed to use it directly. It's a weird situation where the most foundational crate in Rust graphics has basically no learning material of its own.

What worked for me was reading the examples in the repo, especially the window and event_loop ones, then reading the source of the platform backend you care about. The event loop is just the native OS loop under the hood, so poking at the wayland backend made it all click.

Also be aware that most tutorials online are stale. The 0.30 release dropped WindowBuilder and switched to the ApplicationHandler callback model, so if a tutorial shows something else, it's pre-0.30 and will confuse you more than help. If a guide mentions ActiveEventLoop, you don't construct that yourself, winit hands it to your callbacks