lenient_winit

Crates.iolenient_winit
lib.rslenient_winit
version0.2.3
created_at2025-10-12 05:06:16.60427+00
updated_at2025-10-12 05:06:16.60427+00
descriptionA wrapper around winit to provide a nicer experience for developers. Experience similar to SDL2/GLFW
homepagehttps://github.com/RustyCrabs100/lenient_winit
repositoryhttps://github.com/RustyCrabs100/lenient_winit
max_upload_size
id1878919
size70,901
(RustyCrabs100)

documentation

README

lenient_winit

This crate is designed to wrap around winit, as to simplify the experience of many people. Think of this as a more high-level winit, where the main pain points are already dealt with. This acts less like regular winit, but more like an SDL2/GLFW kind of experience. This crate is very simple to use. Simply call the run() function, and you can call poll_events() wherever you wish, excluding the main thread.

How to use

use lenient_winit::window;

fn main() {
    let mut app = window::App::default();
    // Don't do this unless your replacing it with non-default attributes
    // This is just to showcase the function
    app.replace_window_attributes(window::WindowAttributes::default());
    std::thread::spawn(|| {
        'main: loop {
            #[cfg(test)]
            {
                break 'main;
            }
            #[cfg(not(test))]
            for event in window::App::poll_events() {
                match event {
                    _ => {}
                }
            }
        }
    });
    app.run(window::ControlFlow::Poll)
        .expect("Event Loop Error");
}

It really is that simple!

Now, the above example was for our single-window system, our multi-window system uses lenient_winit::multi_window, and is slightly more complicated. It shouldn't be that much different from the single-window system, just that all events are paired with their corresponding WindowId. For our multi-window system, this is how you use it:

use lenient_winit::multi_window;
use lenient_winit::window;

fn main() {
    let mut app = multi_window::MultiApp::default();
    for _ in 1..5 {
        app.add_window_attributes(window::WindowAttributes::default());
    }
    std::thread::spawn(|| {
        'main: loop {
            #[cfg(test)]
            {
                break 'main;
            }

            #[cfg(not(test))]
            for event in multi_window::MultiApp::poll_events() {
                match event {
                    _ => {
                        println!("Testing Testing");
                    }
                }
            }
        }
    });
    app.run(window::ControlFlow::Poll)
        .expect("Event Loop Error");
}

Why would you use this?

  • This crate is a direct wrapper around winit. It does not go through other crates, so your still connected to the winit ecosystem.
  • Since this crate is a wrapper around winit, the hard work that normally you would do, is done by us.
  • So you no longer have to deal with Windowing, Event Loops, and all the other troubles of winit.
  • You can simply poll the events, letting your code be much more ergonomic and free, since you aren't enslaved by winit as it leeches deeper and deeper into your coding.
  • At least with us, the bare minimum you have to change is switching most of your code to different threads, and even then you should've been multi-threading your code anyways.

What is currently available

Currently everything displayed in the documentation is available to be used, and whenever we add new features, anything hidden/missing will be shown in the documentation.

How we update

Whenever we update, it won't be a breaking change, as the things you do using this crate should remain mostly the same. The only changes we will do are normal changes, where only the backend changes. However, sometimes breaking changes are necessary.

If breaking changes are necessary

Whenever a breaking change happens, it's usually 1 of 2 things:

  1. We change it to make it easier for new people to use
  2. Whenever winit changes to the point where abstracting it away from the user is impossible.

Now, we will try to make sure breaking changes don't happen, but if it ever does, a new branch will be made. We will also increment the major version by 1.

Limitations

  • Currently, the only way to use this crate is by using the run() function. While this isn't a limitation if you look at it's surface value, once you take a deep look into it, it's quite the limitation.
  • The run() function, once executed, consumes itself, and takes full control over the main thread. Do any work prior windowing or in a seperate thread.

IMPORTANT NOTE!

Currently, once you use the run() function, the main loop is no longer yours. You can never use it again, which means you have to do the rest of your work in a different thread. We are working to get rid of this, however, the normal run() function will always be available. This is because the method to let the user keep the main loop is NOT cross-platform. The only method we can use is not available for iOS, MacOS, and the Web. This is because the main loop must be owned by the windowing system for these platforms.

This limitation does not come from lenient_winit, but instead from the operating system.

In lenient_winit, we classify breaking changes as anything that significantly impacts the user. This includes making the user change their code significantly, rethink their model, or increase their compile times significantly. What we DON'T classify as a breaking change would be adding a small dependency, removing a non-exposed dependency, or renaming a certain item. The only time we do classify those as breaking changes, is if it breaks the backend, requires more effort than necessary for the change, or noticeably lowers performance.

Examples:

  1. We add tokio as a dependency: Breaking Change, the compile time increases SIGNIFICANTLY
  2. We rename replace_window_attributes with window_attributes: Non-breaking Change, it's a simple find and replace.
  3. We remove winit as a dependency: Breaking Change, what is the point of this crate IF WE DON'T EVEN HAVE WINIT?!?!

Versioning will only apply once this crate gets posted on crates.io, until then, we are free to add and remove things at free will.

Certain items that are necessary to be used have been re-exported, but if anything that you require is not being re-exported, create an issue on our repository or create a pull request and do it yourself. There is also the option to add winit as a direct dependency if necessary.

How we write our versions

Our versions are written as Major.Minor.Patch-Release. Some versions will omit the -Release part of the version, however most will include it. The -Release portion defines the stability of the version. If the -Release portion is missing, then the version is stable. If the -Release portion is included, then the stability is less than reliable, but it's reliability varies depending on the name. This -Release portion can be many things, but these are the 4 main one's we will use.

  1. -prealpha: VERY not stable. Many thing's are still being figured out. Be warned...
  2. -alpha: Not stable, but still usable. This is where either many things are untested or volatile.
  3. -beta: Slightly stable. You can use this with moderate worry about it breaking, but most common uses are stable.
  4. -nightly: This one is special; it can be included alongside the other 3 (written as -Version_nightly). This means that this version uses experimental methods, either lacking testing or being very experimental. If left by itself, it means that this version IS stable, but requires a nightly version of rust.
Commit count: 0

cargo fmt