前言
本专栏是基于rust的GUI库iced的介绍,主要包括iced部件、iced实例的讲解与学习。如果你也对iced感兴趣,不妨来看看。
iced库github地址:
github.com/iced-rs/ice…
发文平台
稀土掘金
环境配置
- 系统:window10
- 平台:visual studio code
- 语言:rust
- 库:iced(0.13)、iced_aw
概述
本文是第一篇,算是一个序章,主要介绍一下如何实现一个典型的iced窗口,同时介绍一下如何实现自定义窗口图标以及如何显示中文字体。
1、创建iced窗口
iced库的作者描述了iced的架构:
并直言是受到了Elm的启发,关于Elm,大家感兴趣的可以去其官网看看: elm-lang.org/
本文不多赘述。
我们主要关注iced的实现,下面将以一个简单的示例来介绍iced。
示例如下:一个简单的计数器,有两个按钮与一个文本标签,点击相应的按钮,文本的内容显示的数值将随之减少或者增加。与此同时,文本的尺寸将与数值的大小关联,也就是会随之变化。
我们首先来看一下iced官方给出的典型示例代码:
官方源码
use iced::widget::{button, column, text, Column};
fn main() -> iced::Result {
iced::run("A cool counter", Counter::update, Counter::view)
}
#[derive(Default)]
struct Counter {
value: i32,
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
Increment,
Decrement,
}
impl Counter {
pub fn view(&self) -> Column<Message> {
// We use a column: a simple vertical layout
column![
// The increment button. We tell it to produce an
// `Increment` message when pressed
button("+").on_press(Message::Increment),
// We show the value of the counter here
text(self.value).size(50),
// The decrement button. We tell it to produce a
// `Decrement` message when pressed
button("-").on_press(Message::Decrement),
]
}
pub fn update(&mut self, message: Message) {
match message {
Message::Increment => {
self.value += 1;
}
Message::Decrement => {
self.value -= 1;
}
}
}
}
如果运行上面的代码,效果如下:
可以看到,这是一个最简单的窗口,尺寸、位置、样式等都未进行设置。但是具备功能。
我们将在这个示例代码上进行修改,首先我们希望能对窗口进行调整,比如设置一个合适的尺寸,窗口的初始位置也希望进行设置。
iced提供对窗口参数的设置选项,即iced::window::Settings
:
/// The window settings of an application.
#[derive(Debug, Clone)]
pub struct Settings {
/// The initial logical dimensions of the window.
pub size: Size,
/// The initial position of the window.
pub position: Position,
/// The minimum size of the window.
pub min_size: Option<Size>,
/// The maximum size of the window.
pub max_size: Option<Size>,
/// Whether the window should be visible or not.
pub visible: bool,
/// Whether the window should be resizable or not.
pub resizable: bool,
/// Whether the window should have a border, a title bar, etc. or not.
pub decorations: bool,
/// Whether the window should be transparent.
pub transparent: bool,
/// The window [`Level`].
pub level: Level,
/// The icon of the window.
pub icon: Option<Icon>,
/// Platform specific settings.
pub platform_specific: PlatformSpecific,
/// Whether the window will close when the user requests it, e.g. when a user presses the
/// close button.
///
/// This can be useful if you want to have some behavior that executes before the window is
/// actually destroyed. If you disable this, you must manually close the window with the
/// `window::close` command.
///
/// By default this is enabled.
pub exit_on_close_request: bool,
}
我们可以看到,window参数包括许多方面,除了size、position,还有可见性、可调整大小、图标icon等,我们并不需要设置所有参数(当然如果你有需要可以全部设置),本例中,我们将调整size、position,以及添加icon。
我们首先修改main函数中的代码,添加对window的设置:
fn main() -> iced::Result {
//iced::run("A cool counter", Counter::update, Counter::view)
iced::application("A cool counter", Counter::update, Counter::view)
.window(iced::window::Settings{
size:iced::Size{width:400.0,height:400.0},
position:iced::window::Position::Specific(iced::Point{x:100.0,y:40.0}),
..Default::default()
})
.run()
}
再运行一下看看:
接下来,我们为窗口添加图标,首先我们看看iced窗口的图标的定义:
pub icon: Option<Icon>,
其中Icon定义:
/// An window icon normally used for the titlebar or taskbar.
#[derive(Debug, Clone)]
pub struct Icon {
rgba: Vec<u8>,
size: Size<u32>,
}
可以看到,Icon事实上就是一个字节数组,相当于图像的底层数据。如果我们要将常用的图片格式如png、jpg等转为iced的图标,也就是需要对图片进行处理,获取其图像数据转为Icon格式即可。
iced官方提供了icon模块,提供了一个from_rgba函数:
/// Builds an [`Icon`] from its RGBA pixels in the `sRGB` color space.
pub fn from_rgba(
rgba: Vec<u8>,
width: u32,
height: u32,
) -> Result<Icon, Error> {
const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4;
if rgba.len() % PIXEL_SIZE != 0 {
return Err(Error::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
return Err(Error::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
});
}
Ok(Icon {
rgba,
size: Size::new(width, height),
})
}
所以,我们只需要提供要设置为窗口图标的图片的字节数组和长宽尺寸,就可以通过from_rgba函数转为Icon。
我们如何获取图片的字节数据呢?我们可以使用image这个图像处理库。
GitHub - image-rs/image: Encoding and decoding images in Rust
image可以处理大部分图片格式:
要使用image,我们需要先添加image:
cargo add image
然后导入它:
extern crate image as eximg
此处我们导入时使用as为image重命名,是因为iced中本身也有image这个元素,以后会用到,所以为了防止名称冲突,此处为外部的image库重命名为eximg。
好了,现在我们使用image库来对图片进行处理,我们可以创建一个函数img_to_icon:
///
/// 将图片转为icon格式图标
///
pub fn img_to_icon(path:&str) -> Icon{
let img2=eximg::open(path);
let img2_path=match img2 {
Ok(path)=>path,
Err(error)=>panic!("error is {}",error),
};
let img2_file=img2_path.to_rgba8();
let ico2=icon::from_rgba(img2_file.to_vec(), 32, 32);
let ico2_file=match ico2{
Ok(file)=>file,
Err(error)=>panic!("error is {}",error),
};
ico2_file
}
主要逻辑就是利用image将图片转为vec< u8>数组,然后再利用from_rgba将vec< u8>转为Icon格式。
现在,我们在主程序中调用此函数,然后在window中添加icon项,并赋予转换后的图标:
let myicon=imgsetup::img_to_icon("./icons/雪花.png");
iced::application("A cool counter", Counter::update, Counter::view)
.window(iced::window::Settings{
size:iced::Size{width:400.0,height:400.0},
position:iced::window::Position::Specific(iced::Point{x:100.0,y:40.0}),
icon:Some(myicon),
..Default::default()
})
.run()
运行一下看看:
好了,图标问题解决了(图片转换时需要注意图片尺寸问题,不过这个问题我们留待以后再说),现在我们对view函数中的代码稍作修改,将按钮的文本改为中文试试:
pub fn view(&self) -> Column<Message> {
// We use a column: a simple vertical layout
column![
// The increment button. We tell it to produce an
// `Increment` message when pressed
button("增加").on_press(Message::Increment),
// We show the value of the counter here
text(self.value).size(50),
// The decrement button. We tell it to produce a
// `Decrement` message when pressed
button("减少").on_press(Message::Decrement),
]
}
运行看看:
可以看到,按钮的文本显示为乱码,这是因为iced的默认字体不支持中文,所以,我们可以修改其默认字体,比如设置为微软雅黑:
let myfont = "微软雅黑";
let myicon=imgsetup::img_to_icon("./icons/雪花.png");
iced::application("A cool counter", Counter::update, Counter::view)
.window(iced::window::Settings{
size:iced::Size{width:400.0,height:400.0},
position:iced::window::Position::Specific(iced::Point{x:100.0,y:40.0}),
icon:Some(myicon),
..Default::default()
})
.settings(iced::Settings{
default_font:Font::with_name(myfont),
..Default::default()
})
.run()
再运行看看:
2、调整布局,文本大小改变
在上面,我们已经实现了一个基本的iced窗口,并为其添加了图标,增加了中文支持。现在,我们对上述窗口的布局稍作调整,并增加功能,当数值变化时,文本的尺寸也相应变化。 我们先修改view中的代码:
// We use a column: a simple vertical layout
column![
row![
// The increment button. We tell it to produce an
// `Increment` message when pressed
button("增加").on_press(Message::Increment),
// We show the value of the counter here
text(self.value).size(50),
// The decrement button. We tell it to produce a
// `Decrement` message when pressed
button("减少").on_press(Message::Decrement),
],
text(format!("当前size:{}",self.textsize)).size(self.textsize),
].padding(10).spacing(10)
我们增加一个row排布(关于布局以后再说,此处只是使用),并新增了一个text元素用于显示尺寸变化,text的size参数内,使用的是变量textsize,因此,我们需要在struct中新增变量:
#[derive(Debug)]
struct Counter {
value: i32,
textsize:u16,
}
但是,如果直接运行会报错,因为text元素的文本内容的尺寸不能为0,也就是文字的size至少要大于0,所以我们需要给变量textsize一个初始值:
fn new() -> Self {
Self {
value: 10,
textsize:10,
}
}
我们增加一个new函数,这个函数的作用就是为Counter结构体的变量赋予初始值。不过,需要注意,new函数并非iced中的“默认”函数,而是需要我们自定义添加的,因此,如果我们希望new作为初始值函数,就需要对其进行定义,rust中提供了Default这个特性,可以为类型返回一个默认值,我们可以利用此特性。
impl Default for Counter {
fn default() -> Self {
Self::new()
}
}
这样,我们就具有了初始值,运行看看效果:
但目前还只是初始值,如果我们点击按钮,value的值是会变化,但并没有将其与textsize联系起来,所以,我们还需要更改update中的代码:
Message::Increment => {
self.value += 1;
if self.value >= 50 {
self.textsize = 50;
} else {
self.textsize = self.value as u16;
}
}
Message::Decrement => {
self.value -= 1;
if self.value <=5 {
self.textsize = 5;
} else {
self.textsize = self.value as u16;
}
}
主要目的是将当前的value值再赋予textsize变量,但由于字体尺寸既不能小于0,也不适合无限大,所以,对其进行了范围限制。
3、动态演示
这样一来,当我们点击按钮时,计数值会变化,而下面的text文本尺寸也会随之变化,我们可以看一下实例演示效果: