前言

学习一门编程语言,绝对不可以抛弃该编程语言的应用。在学习其他编程语言时,例如C++,只学习语法,数据结构与算法是相当枯燥的,这就很考虑一个人的毅力了。此时最好的办法就是让学习变得有趣起来,在我学习的时候,我的兴趣之源就是想要做出Windows上华丽的窗口,来提高自己的学习和工作效率,为此我学习过QT,MFC,这些框架都挺好,确实实现了我想要的效果,但是开发较为繁琐,后来我又学习qt python,确实开发变得很方便了,但是新的问题又出现了,就是打包不方便,为此,我在这个路上经历了曲折的探索。此时学习Rust也是一样的,要想提升自己的学习力,就要找到其中感兴趣的点,对于Rust,我很想利用Rust的优势来实现Windows的窗口,也找过相关的解决方案,比如Rust的qt,但是它比较繁琐,在后面的文章会介绍到;还有使用Rust作为后端的跨平台ui框架Tauri UI,他的思路更像是Electron这种的,前端使用html+css布局,然后后端使用Rust,再打包好app,其使用方式在本系列文章后面也会介绍;还有就是本期文章要介绍的Iced,它更像是Rust的flutter,基于Elm实现的跨平台GUI框架。

Iced是一个我较为感兴趣的GUI框架,其开发方式对我我这种学习了Vue的人来说相当友好,且配和Rust的特点,已经是很舒服了。此外它颜值也挺高,这就是我学习它的理由。

Iced的特点

  • 简单易用,有一系列内置API
  • 类型安全,有一套交互编程模型
  • 跨平台(支持Windows,Mac,Linux和Web)
  • 响应式布局
  • 基于widgets
  • 支持自定义widgets
  • 还有其他特性,建议去Github查看

一、搭建项目

1. 正常创建项目

首先创建一个项目

cargo new gui_iced_001

2. 导入idea

使用idea导入项目

3. 引入依赖

打开Cargo.toml,然后在依赖出写入

iced = "0.4"

注意:本人用的是2021以后的版本,如果你不是,建议去官网学习对应的处理策略,这里不解释。

此时文件应该是这样的

到此为止,项目就搭建完毕,接下来就是写我们的demo了。

二、编写demo

在Github上是有很多Iced的例子的,其中最经典,也是官网唯一写上去的例子就是Counter计数器,因从这里就实现Counter的demo。虽然说这个很简单,但是其中坑很多,好不容易才写出这个demo,就说两个比较坑的地方把,这个框架行和列不分,例子中代码太过老旧,都是我一步一步探索出来的。

以下内容与官网会有较大的差别,官网的运行不了,请注意甄别。

1. 编写State

首先为程序编写一个State,这个核心概念在学习Vue和React或者Flutter的时候必然会用到,这里暂时不做解释,直接给出代码

struct Counter {
    value: i32,
    // The local state of the two buttons
    increment_button: button::State,
    decrement_button: button::State,
}

这里是定义了一个结构体Counter ,这个结构体就当作我们窗口的State,其成员value代表计数器计数的数值,increment_buttondecrement_button是两个按钮+和-的state。

2. 定义消息类型

接下来就是定义程序中用到的消息类型,程序中的交互是通过信号来进行的,这点Qt就做的很好,如果你学习过qt或者前端的Vue等框架,这个就很好理解了。

#[derive(Debug, Clone, Copy)]
enum Message {
    IncrementPressed,
    DecrementPressed,
}

这里定义了两个消息,一个是IncrementPressed,代表+按钮被点击,一个是DecrementPressed,代表-按钮被点击。

3. 编写视图逻辑

这里官方给出的例子是直接为Counter 实现view和update,但是经过我的探索,是不可以直接使用的。

Iced程序运行,需要实现Application或者Sandbox,这俩是什么意思现在先不管,我们这里使用的是Sandbox,因为其足够的简单。

一个空的Sandbox应该是这样的

impl Sandbox for Counter {
    type Message = ();

    fn new() -> Self {
        todo!()
    }

    fn title(&self) -> String {
        todo!()
    }

    fn update(&mut self, message: Self::Message) {
        todo!()
    }

    fn view(&mut self) -> Element<'_, Self::Message> {
        todo!()
    }
}

这里从上到下开始介绍

Message

代表的是当前窗口的所有消息,在使用时,需要传入定义好的消息枚举,就比如说这里时用法应该是这样的,

#[derive(Debug, Clone, Copy)]
enum Message {
    IncrementPressed,
    DecrementPressed,
}
// ....
type Message = Message;

new

这里和通常编写代码一样,需要返回自身实例,就不做过多解释了

fn new() -> Self {
        Self { value: 0, increment_button: Default::default(), decrement_button: Default::default() }
    }

title

见名知义,这里就是要返回当前窗口的名字

fn title(&self) -> String {
        String::from("Counter - Iced")
    }

update

这里时处理窗口的消息逻辑,本窗口处理两个消息,一个是IncrementPressed,代表+按钮被点击,如果被点击了,State的Value就+1,一个是DecrementPressed,代表-按钮被点击,如果被点击了,State的Value就-1。这里处理相当简单,并未考虑边界值。

fn update(&mut self, message: Message) {
        match message {
            Message::IncrementPressed => {
                self.value += 1;
            }
            Message::DecrementPressed => {
                self.value -= 1;
            }
        }
    }

view

这里要返回窗口的布局,实际上也就是要构建这个窗口,这里Counter的窗口代码如下

fn view(&mut self) -> Element<Message> {
        Column::new().push(
            Text::new("Counter")
        ).push(
            Row::new().push(
                Button::new(&mut self.increment_button, Text::new("+"))
                    .on_press(Message::IncrementPressed).width(Length::Fill),
            ).push(
                Text::new(self.value.to_string()).size(22),
            ).push(
                Button::new(&mut self.decrement_button, Text::new("-"))
                    .on_press(Message::DecrementPressed).width(Length::Fill),
            )
        )
        .align_items(Alignment::Center).into()
    }

可以看到,这里使用的时链式调用,一层套一层,其代码和flutter特别的相似,如果你学习过flutter,那必然非常熟悉,这里我画一个图,来解释这段代码,首先由Column将窗口分为两行,其布局和红框是一致的,上下两行

注意:这个框架行和列傻傻分不清,Column是列的意思,在这个框架里面是行的意思。

第一行,只添加了个Text组件,并且给初始值Counter,这里原本是打算使用中文计数器的,奈何这玩意儿不支持中文

Column::new().push(
            Text::new("Counter")
        )

第二行,其中添加了个Row组件(组件),并且加入了三个组件,分别是两个Button,就是+和-按钮,一个Text组件,用来显示当前Value

//...
.push(
            Row::new().push(
                Button::new(&mut self.increment_button, Text::new("+"))
                    .on_press(Message::IncrementPressed).width(Length::Fill),
            ).push(
                Text::new(self.value.to_string()).size(22),
            ).push(
                Button::new(&mut self.decrement_button, Text::new("-"))
                    .on_press(Message::DecrementPressed).width(Length::Fill),
            )
        )

所以此时的窗口布局应该是这样的

4. 编写main函数

写好的窗口是无法自动运行的,需要启动才可以,通常在main函数中启动窗口,在这里会变得简单起来,这里直接贴上代码

fn main() -> iced::Result {
    Counter::run(Settings::default())
}

启动窗口只要调用窗口的run方法就好了,其中传入Settings,用来设置窗口的初始状态,这里直接用默认状态了,如果后续还要进入深入,这里会专门出一期来进行讲解。

三、运行效果

此时我们运行当前写好的demo

注意:完整代码放在文章末尾,如果你懒得敲代码,可以直接复制。


总结

本期为大家介绍了Rust的GUI框架Iced,经过我的探索,终于是将Counter Demo搭建起来。经过我的体验,我认为这个框架其实并没有我想象中的那么好,它确实是Rust的原生GUI框架,也确实有他说的那些特点,确实有颜值,但是它有个最大的问题就是不支持中文,而且行和列傻傻分不清,官方文档太过老旧,所以搭建demo流程就变得很复杂,对开发不是很友好,唯一值得一说的就是这个代码,确实是舒服了不少,这是其他UI框架所无法比的上的,这点值得赞同,希望以后这个框架或者后继者能解决这些问题,Rust的UI才能强大起来。

完整代码

use iced::{Alignment, button, Button, Column, Element, Length, Row, Sandbox, Settings, Text};

fn main() -> iced::Result {
    Counter::run(Settings::default())
}

struct Counter {
    value: i32,
    // The local state of the two buttons
    increment_button: button::State,
    decrement_button: button::State,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    IncrementPressed,
    DecrementPressed,
}

impl Sandbox for Counter {
    type Message = Message;

    fn new() -> Self {
        Self { value: 0, increment_button: Default::default(), decrement_button: Default::default() }
    }

    fn title(&self) -> String {
        String::from("Counter - Iced")
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::IncrementPressed => {
                self.value += 1;
            }
            Message::DecrementPressed => {
                self.value -= 1;
            }
        }
    }

    fn view(&mut self) -> Element<Message> {
        Column::new().push(
            Text::new("Counter")
        ).push(
            Row::new().push(
                Button::new(&mut self.increment_button, Text::new("+"))
                    .on_press(Message::IncrementPressed).width(Length::Fill),
            ).push(
                Text::new(self.value.to_string()).size(22),
            ).push(
                Button::new(&mut self.decrement_button, Text::new("-"))
                    .on_press(Message::DecrementPressed).width(Length::Fill),
            )
        )
        .align_items(Alignment::Center).into()
    }
}


Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐