深入解析 Rust Iced GUI 应用架构并实现一个沙盒示例

科技   2024-09-12 23:55   广东  

在过去的几周里,我一直在探索 Rust 中的 GUI 应用开发,并发现 Iced 是一个非常简洁且极简主义的框架,非常适合创建轻量级 GUI 应用。在本文中,我将深入探讨 Iced 的架构,并使用该框架构建一个简单的 GUI 应用。

架构解析

在 GUI 中,有些控件(Widget)是交互式的,例如按钮;而另一些则不是,例如标签。当按下按钮时,它会触发一个交互,该交互可以驱动后端执行某些操作,例如更新标签中显示的文本。这种交互是有状态的,因为它会改变控件的状态。

我们的用户界面中有三个要素:

  • 控件:界面的可视元素。
  • 交互:某些控件可能触发的操作。
  • 状态:界面的底层信息。

这些元素相互连接,形成一个紧密的反馈回路。

控件在用户与其交互时产生交互。然后,这些交互会改变应用程序的状态。更改后的状态会传播并指示必须显示的新控件。

GUI 三要素

GUI 三要素

  • 模型:应用程序的状态。
  • 消息:应用程序的交互。
  • 更新逻辑:消息如何改变状态。
  • 视图逻辑:状态如何决定控件。

当我们逐步完成 Hello World GUI 应用的实现时,这些概念将变得更加清晰。

您可以阅读 Iced 作者对此架构的解释:https://book.iced.rs/architecture.html (本节是我个人的理解)。

代码实现

让我们创建一个最小的 Iced 应用。

cargo new - bin iced-simple-sandbox-app

将 Iced 依赖项添加到 cargo.toml 文件中。

[package]
name = "iced-simple-sandbox-app"
version = "0.1.0"
edition = "2021"
[dependencies]
iced = {version = "0.12.1"}

该应用程序包含一个标签,用于显示变量(状态)的值,并包含两个按钮:“递增”和“递减”。当用户按下“递增”按钮时,变量的值增加 1。类似地,按下“递减”按钮会将值减少 1。

在 Iced 术语中,我们的模型封装了应用程序的状态,即递增或递减的变量。

struct AppState {
    index: i32,
}

消息对应于用户与应用程序的交互,例如按下“递增”或“递减”按钮。

#[derive(Debug, Clone)]
enum Message {
    IncrementButtonPressed,
    DecrementButtonPressed,
}

Iced 提供了一个名为 Sandbox 的特征(trait),我们的应用程序将实现该特征。Iced 运行时使用此特征调用适当的函数并运行应用程序。以下是 Sandbox 特征。

请注意,_Sandbox_ 特征支持的功能最少。还有一个名为 Application 的特征,它提供了更灵活的功能,例如运行异步操作。我们将在以后的文章中介绍 _Application_。

pub trait Sandbox {
    type MessageDebug + Send;

    // 必需方法
    fn new() -> Self;
    fn title(&self) -> String;
    fn update(&mut self, message: Self::Message);
    fn view(&self) -> Element<'_, Self::Message>;

    // 提供的方法
    fn theme(&self) -> Theme { ... }
    fn style(&self) -> Application { ... }
    fn scale_factor(&self) -> f64 { ... }
    fn run(settings: Settings<()>) -> Result<(), Error>
       where Self'static + Sized { ... }
}
  • new() 方法在应用程序初始化时调用。
  • 每次 UI 更新时都会调用 title() 方法。它返回一个用作应用程序标题的 _String_。
  • 每当发生用户交互时,都会触发 update() 方法,其中 Message 参数指示交互的类型。这将更新应用程序的状态。
  • view() 方法在应用程序首次运行或用户与其交互时调用。它返回一个泛型类型 _Element_。Element 是可组合的。我们将在其他文章中讨论这一点。

Sandbox 特征中的其他方法都有默认实现,因此我们不需要提供它们。但是,我包含了 theme() 方法的实现,因为深色主题增添了时尚、现代的感觉。

状态图如下所示

Iced Sandbox 状态图

让我们实现应用程序。

impl Sandbox for AppState {
    type Message = Message;

    fn new() -> AppState {
        println!("{:?} Sandbox::new()", std::thread::current().id());
        Self {
            index: 0,
        }
    }

    fn title(&self) -> String {
        println!("{:?} Sandbox::title()", std::thread::current().id());
        String::from("AppState")
    }

    fn update(&mut self, message: Self::Message) {
        println!("{:?} Sandbox::update({:?})"
            std::thread::current().id(), message);
        self.index = match message {
            Message::IncrementButtonPressed => {self.index + 1},
            Message::DecrementButtonPressed => {self.index - 1},
        };
    }

    fn view(&self) -> Element<Self::Message> {
        println!("{:?} Sandbox::view()", std::thread::current().id());
        let incerement_btn = button(
            "Increment"
        )
        .width(150)
        .on_press(Message::IncrementButtonPressed);

        let label_text = text("Value:").width(75);
        let index_text = text(self.index.to_string()).width(75);

        let decrement_btn = button(
            "Decrement"
        )
        .width(150)
        .on_press(Message::DecrementButtonPressed);

        let content = column![
            incerement_btn,
            row![label_text, index_text,],
            decrement_btn,
        ]
        .width(Length::Fill)
        .align_items(Alignment::Center)
        .spacing(10);

        container(scrollable(content))
            .width(Length::Fill)
            .height(Length::Fill)
            .center_x()
            .center_y()
            .into()
    }
}

应用程序如下所示

应用程序截图

感谢您的阅读!关注公众号并回复 ”iced“ 获取源码

文章精选

Tailspin:用 Rust 打造的炫彩日志查看器

Rust: 重塑系统编程的安全壁垒

Youki:用Rust编写的容器运行时,性能超越runc

使用C2Rust将C代码迁移到Rust

Rust语言中如何优雅地应对错误


Rust编程笔记
与你一起在Rust的世界里探索、学习、成长!
 最新文章