全文字数:3198词
阅读时长:大约5分钟(不包括所有代码)
使用Rust编写快速、轻简的GUI程序:fltk-rs crate
这是一篇推荐你使用Rust的文章……
“Rust,一门赋予每个人构建可靠且高效软件能力的语言。”
截至2021年,在Stack Overflow的年度 Overview 上,Rust 已经连续六年成为最受开发者喜欢的编程语言!
Rust作为一门具有零开销抽象的系统编程语言,具有以生命周期和所有权借用的内存安全机制,使代码只要通过编译就没有内存安全问题。再加上rust具有极其活跃友好的社区,丰富的crate库,完备且大部分免费的参考资料,这让世界上的多数开发者喜欢上这门语言!正如Rust官网的介绍所说,作为一门编程语言,其最大特点在于“赋能”!
在学习编程语言时,尤其是在学习Rust这种学习曲线 极其陡峭(可能是目前最难学的)的语言时,保持自己对学习的兴趣很重要。而有什么能比编写出能看得到界面的程序更简单、能让人激动的呢?
于是,我利用业余时间将fltk-rs book翻译为中文(翻译水平不足,请大家谅解),方便大家学习fltk-rs,并使用Rust写出一个快速漂亮的程序!
由于国外网络有时不能访问,大家在公众号消息界面回复“fltk-rs”即可获得该电子书的中文和英文pdf文件,如果可以的话,请在GitHub上点个Star哦!
FLTK for Rust?
在 Crates.io 上,Rust已经有很多跨平台的GUI框架可供使用了,比如 Iced,Frui,egui 等等。这里我们为什么要选择从FLTK开始学习呢?
最该放在前面的理由就是“简单”!
FLTK,全名 Fast Light Toolkit。而它正如它的名字一样,又小又快。FLTK库本身是使用 C++ 98开发的,fltk-rs则是使用Rust实现的,它通过FFI(Foreign Function Interface)调用一个FLTK封装器 cfltk(该库使用 C89 和 C++11编写),来达到使用rust编写fltk程序的目的。
小知识:C++之父也在使用FLTK哦!
现在说说我们使用fltk的好处:
- 构造简单,对习惯使用面向对象GUI库的开发者及其友好
- 文档齐全,通过查阅文档可以解决几乎所有问题
- 又轻又快,编译出的文件很小(iced一个测试文件占用100M以上,fltk则不到1M),运行时占用内存小,且快速
- 跨平台,无需动态链接,这让你一次程序可以在Window,Linux,macOS甚至Android上运行(Android还是写原生比较好)
- 组件丰富,具有图像支持,开发方式丰富,开源协议宽松(MIT License)
fltk也有些许缺点,比如对于复杂界面的编写没有很好的支持组件等等,但这些无伤大雅,对于入门rust GUI编写已经绰绰有余了。
下面,就让我们开始简单地学习下fltk-rs吧!
下面的内容可能有些草率,但这是让你看一下如何使用fltk-rs编写GUI程序。更全面地学习请参见fltk-rs官方文档和fltk-rs book。
配置
首先,确保你配置了 g++,CMake,git和make(linux上),同时可能还要安装一些必须的库。关于库的安装个其他平台工具链的配置请参阅fltk-rs book的Setup配置章节。
将以下代码添加到你的 Cargo.toml 文件:
1 | [dependencies] |
这基本上就可以了,Cargo会帮你下载和导入所有的库。
在这里我曾经踩到个坑,Cargo在下载库时可能会因为网络问题导致没有完全下载好所有需要的crate。但是,它不会再重新下载了,而且不会再给出提醒,这将导致一系列的链接错误!唯一的解决办法只有清楚cargo目录下缓存的crate文件,然后重新 cargo build
下载,注意提示信息并保持网络通畅!
如果你也遇到了链接错误的问题,仔细想想cargo build
时有没有出错呢。
创建一个APP结构
fltk-rs crate在app模块中提供了一个App结构。初始化App结构可以初始化所有内部样式、字体和支持的图像类型。它还初始化了程序将要运行的多线程环境。
1 | use fltk::*; |
run方法运行gui应用程序的事件循环(event loop)。 要对事件进行精细的控制,可以使用wait()方法:
1 | use fltk::*; |
此外,App结构允许你使用with_scheme()初始器来设置程序的全局主题:
1 | use fltk::*; |
这将使你的程序具有Gtk程序的样子。还有其他的内置方案,Basic、Plastic和Gleam。
App结构还负责在应用程序开始时使用load_system_fonts()方法加载系统字体。
一个典型的fltk-rs应用程序,将在创建任何部件和显示主窗口之前构建App结构。
任何在run()方法调用后添加的逻辑,将在事件循环结束后执行(通常是关闭应用程序的所有窗口时,或者调用quit()方法时)。该逻辑可能包括在必要时重启程序的逻辑。
除了App结构外,App模块本身还包含与你的程序的全局状态有关的结构和自由函数。其中包括设置背景和前景颜色、默认字体和大小等视觉效果、屏幕功能、剪贴板功能、全局处理器、应用事件、通道(channels)(发送器和接收器)和超时。
做出一个窗口
FLTK会在它支持的系统平台上调用原生窗口,然后基本上通过自己的方法来绘制。它会在windows上调用HWND,在MacOS上调用NSWindow,在X11系统(linux, BSD)上调用XWindow。
使用这样的代码可以创建一个window:
1 | use fltk::{prelude::*, *}; |
调用new()函数需要五个参数:
x
以电脑屏幕最左侧为原点的水平距离。y
以电脑屏幕最左侧为原点的垂直距离。width
window的宽度。height
window的高度。title
window标题。
接下来注意对end()的调用。window,以及其他类型的widget,实现了GroupExt trait。实现该trait的这些部件将 持有 任何在call()和end()间创建的widget(通过new()创建串口时,隐式调用了begin()),或者作为其父widget。 下一个调用show()唤起了window,使其出现在显示屏上。
放几个组件
FLTK提供了大约80个窗口组件。这些组件都实现了WidgetBase和WidgetExt的基本trait集。 我们已经遇到了我们的第一个组件,Window。 正如我们在Window widget中所看到的,小组件也可以根据其功能实现其他trait。 在我们之前写的例子中添加一个按钮:
1 | use fltk::{prelude::*, *}; |
注意,这个按钮的父组件是my_window,因为它是在begin()和end()之间创建的。 另一种添加组件的方法是,使用实现了GroupExt trait的widget所提供的add(widget)方法。
1 | use fltk::{prelude::*, *}; |
另一件要注意的事情是按钮的初始化,它的构造函数基本上与Window相同,这是因为它实现了WidgetBase trait。注意,虽然Window的x和y坐标是相对于屏幕的,但按钮的x和y坐标却是相对于包含按钮的窗口的。你可能已经注意到,这也适用于我们在前一页的嵌入式窗口。
下面我们来看几个其他的示例,具体编写方法和原理请参见官方文档或book!
菜单和按钮的示例
这里我们使用闭包设置回调编写这个示例,实际上,fltk还可以允许你使用函数对象来处理回调,或者直接使用sender和message来编写。
1 | use fltk::{enums::*, prelude::*, *}; |
一个逐渐消失的动画
我们使用了线程
1 | use fltk::{enums::*, prelude::*, *}; |
一个绘画板程序
1 | use fltk::{ |
一个flutter风格的计数器
FLTK在风格化应用方面提供了许多东西,这里通过WindowExt trait完成Styling:
1 | use fltk::{ |