学习一门编程语言,绝对不可以抛弃该编程语言的应用。在学习其他编程语言时,例如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的特点,已经是很舒服了。此外它颜值也挺高,这就是我学习它的理由。
首先创建一个项目
cargo new gui_iced_001
使用idea导入项目
打开Cargo.toml,然后在依赖出写入
iced = "0.4"
注意:
本人用的是2021以后的版本,如果你不是,建议去官网学习对应的处理策略,这里不解释。
此时文件应该是这样的
到此为止,项目就搭建完毕,接下来就是写我们的demo了。
在Github上是有很多Iced的例子的,其中最经典,也是官网唯一写上去的例子就是Counter计数器,因从这里就实现Counter的demo。虽然说这个很简单,但是其中坑很多,好不容易才写出这个demo,就说两个比较坑的地方把,这个框架行和列不分,例子中代码太过老旧,都是我一步一步探索出来的。
以下内容与官网会有较大的差别,官网的运行不了,请注意甄别。
首先为程序编写一个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_button
和decrement_button
是两个按钮+和-的state。
接下来就是定义程序中用到的消息类型,程序中的交互是通过信号来进行的,这点Qt就做的很好,如果你学习过qt或者前端的Vue等框架,这个就很好理解了。
#[derive(Debug, Clone, Copy)]
enum Message {
IncrementPressed,
DecrementPressed,
}
这里定义了两个消息,一个是IncrementPressed
,代表+按钮被点击,一个是DecrementPressed
,代表-按钮被点击。
这里官方给出的例子是直接为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!()
}
}
这里从上到下开始介绍
代表的是当前窗口的所有消息,在使用时,需要传入定义好的消息枚举,就比如说这里时用法应该是这样的,
#[derive(Debug, Clone, Copy)]
enum Message {
IncrementPressed,
DecrementPressed,
}
// ....
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")
}
这里时处理窗口的消息逻辑,本窗口处理两个消息,一个是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;
}
}
}
这里要返回窗口的布局,实际上也就是要构建这个窗口,这里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),
)
)
所以此时的窗口布局应该是这样的
写好的窗口是无法自动运行的,需要启动才可以,通常在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()
}
}