前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust winit 0.30.0版本简介

Rust winit 0.30.0版本简介

作者头像
w4ngzhen
发布2024-05-16 16:41:24
1230
发布2024-05-16 16:41:24
举报
文章被收录于专栏:编译思想编译思想

不久前,Rust著名的跨平台窗体管理库winit发布了它的0.30.0版本,较之前的0.2x.x版本,新增了19个的模块API,改动大约19个模块API,移除了大约8个模块API。可见本次升级改动之大,主要是对事件循环、窗口管理的重构。鉴于目前网上较多的文章都是基于0.2x版本的winit的代码,存在时效性问题,所以我决定写一篇文章,对winit的0.30.0版本做一个简单的介绍,同时也为后面的Rust Wgpu系列文章做铺垫。

关于0.2x版本winit

为了呈现清晰的对比,我们先给一关于0.2x版本的winit编写一个应用程序,运行并展示一个窗口:

010-v0_2x_winit
010-v0_2x_winit

0.2x版本的winit的运行模型主要基于过程式:

  1. 创建事件循环
  2. 创建该事件循环关联的窗体
  3. 启动事件循环

尽管使用起来比较简单,但是实际的应用场景会比较复杂,考虑到多窗体情况,这块的代码会愈发的复杂,需要用户做出适当的封装,才能让代码更加的清晰。

关于0.30.0版winit

关于0.30.0版本的winit,则新增ApplicationHandler,来对整个应用程序进行抽象,并把窗体创建、事件处理,收敛到了应用程序这个抽象中,提供更加直观的API。具体是怎样呢?话不多说,让我们通过代码实践来理解。首先初始化一个项目(这里不再赘述,请读者自行创建基础空项目),添加0.30.0版本winit依赖:

代码语言:javascript
复制
[dependencies]
winit = { version = "0.30.0" }

接着,为了后续项目结构的划分,我们在main.rs同级目录下创建一个名为app.rs,内容如下:

代码语言:javascript
复制
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;

pub struct App {}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {}

    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {}
}

在新版的winit中,我们首先定义一个自定义结构体:App,它代表了我们运行的应用程序;接着,我们为App实现来自winit 0.30.0中的新trait:ApplicationHandler。该trait有两个必须实现的方法:resumedwindow_event方法。

先看window_event方法。该在窗口事件发生时被调用,这块其实就是0.2x版本中事件循环中的触发事件的封装。但值得注意的是,在该方法的2个入参:

  1. event_loop: &ActiveEventLoop
  2. window_id: WindowId

这两个参数从含义上讲,代表了当前正激活的事件循环以及与之匹配的窗口。这里就不难理解,winit的0.30.0的新模型,主要是为了以友好的接口方式来支持多窗体、多事件循环。我们可以通过该事件回调,来得到当前是哪个窗体触发,在哪个激活的事件循环中触发的窗体事件。

再看resumed方法。官方文档:ApplicationHandler#resumed,笔者简单总结下:

  1. 所有的平台(桌面端、Android、iOS以及Web)都会 Resumed 事件,各个平台触发该事件是对应了相关平台应用生命周期的某个阶段(例如,iOS中对应applicationDidBecomeActive)。
  2. 考虑多平台可以移植性,推荐建议应用程序在收到第一个 Resumed 事件后仅初始化其图形上下文并创建窗口。由于系统平台的事件驱动具体实现的差异,可能会调用多次,要做“幂等”处理,确保在收到 Resumed 事件后仅初始化一次图形上下文和窗口(比如,iOS上只要激活了就会触发一次,如果没做幂等处理,就会在每次激活时都初始化一次图形上下文和窗口)。

鉴于上述说明,我们在App结构体中增加一个字段:window: Option<winit::window::Window>,稍后我们会在resumed方法中创建窗口,并把它存储在这个字段中,同时给App加上Default特性以便于快速创建App实例:

代码语言:javascript
复制
+ // 添加 Default 以便App::default()来快速创建App实例
+ #[derive(Default)]
pub struct App {
+   window: Option<winit::window::Window>,
}

接着,我们在resumed方法中创建窗口,并把它存储在window字段中:

代码语言:javascript
复制
impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        // 如果窗口为创建,我们就创建一个窗口
        if self.window.is_none() {
            let win_attr = Window::default_attributes().with_title("demo");
            let window = event_loop.create_window(win_attr).unwrap();
            self.window = Some(window);
        }
    }
    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {}
}

上述的代码,通过判断self.window.is_none(),我们可以避免重复创建窗口。

至此,我们的app.rs中的代码就编写完毕了。接下来,我们需要在main.rs中增加创建App以及运行该应用的代码:

代码语言:javascript
复制
use winit::event_loop::EventLoop;
use crate::app::App;

mod app;

fn main() {
    let event_loop = EventLoop::new().unwrap();
    let mut app = App::default();
    event_loop.run_app(&mut app).expect("run app error.");
}

其实,读者可以感受到,新版本的winit下的应用程序运行模型,更加好进行模块的划分了。通过ApplicationHandler,我们将整个应用程序的生命周期抽象出来,并通过事件回调的方式,来处理窗体事件。

上述代码运行以后,会在桌面出现一个窗体,不过此时你还无法点击窗体关闭按钮关闭它。因为我们没有实现对应的窗体退出逻辑,让我们在前面的ApplicationHandlerwindow_event方法中,处理下退出事件:

代码语言:javascript
复制
impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        // ...
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
        match event {
            // 处理退出
            WindowEvent::CloseRequested => {
                event_loop.exit();
            }
            _ => ()
        }
    }
}

至此,我们就能显示一个空白的窗体,并能通过点击关闭按钮关闭它。当然,有读者在macOS关闭窗体时,会出现如下panic:

代码语言:javascript
复制
a delegate was not configured on the application
stack backtrace:
   0: rust_begin_unwind
   ... ...

这是0.30.0的BUG,具体可以参考issue,该问题会在0.30.1版本修复。

写在最后

在本文中,笔者对winit的0.30.0版本的主要变动进行简单的介绍,更多的内容还需要读者自行阅读官方文档以及examples。当然,相信通过本篇文章,不难看出,新版的winit,对其运行模型架构进行了重构,使得其更加易于使用,更符合现代GUI框架的运行模型思路。

但是,由于其架构升级,导致一些现阶段网络上一些经典的文章,可能无法在新版的winit下正确运行,例如《学习 Wgpu》就还是使用的0.29版本。笔者后续会开启关于Rust Wgpu系列文章,会使用新版winit来进行项目的搭建,并且讲解其中一些在新版winit下的Wgpu构建的注意点,敬请期待。

本文完整代码就不单独放库了,主要是概念讲解。读者可以直接参考官方文档的简单例子

PS:笔者虽然还没有编写Rust Wgpu系列文章,但其基于winit 0.30.0版本的example已经在开发编写中了,笔者可以在这个仓库中checkout代码:w4ngzhen/wgpu_winit_example,也欢迎读者给个star,十分感谢。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-05-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于0.2x版本winit
  • 关于0.30.0版winit
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档