注:此文适合于对rust有一些了解的朋友
iced是一个跨平台的GUI库,用于为rust语言程序构建UI界面。

这是一个系列博文,本文是第三篇,前两篇的链接:
1、Rust UI开发(一):使用iced构建UI时,如何在界面显示中文字符
2、Rust UI开发(二):iced中如何为窗口添加icon图标
本篇是系列第三篇,主要关注如何在窗口上显示图片,要在窗口显示一张图片,基本上需要解决两个问题,一是图片文件导入,二是图片文件显示。这两个功能对于其他成熟语言都不是问题,文件对话框和图片渲染都不是难事,但iced是缺少对话框部件的。
所以,就要借助于第三方库,下面我们将针对这两个方面做说明。
实际窗口效果预览:

一 文件对话框
至少目前为止(iced=0.10)iced中没有集成对话框功能,包括文件对话框、字体、颜色、消息等对话框都没有,但我看到其他支持rust的GUI库如egui、nwg(native-window-gui)等都是有对话框的,当然egui中是用rfd库来实现的。
所以,在本篇中,我们也是利用rfd来实现文件对话框功能。

rfd是Rusty File Dialogs的简写,是跨平台的rust库,提供打开/保存对话框的功能。
rfd的官方代码:
rust
use rfd::FileDialog;
let files = FileDialog::new()
.add_filter("text", &["txt", "rs"])
.add_filter("rust", &["rs", "toml"])
.set_directory("/")
.pick_file();
使用起来也很简单,在你的项目的Cargo.toml中添加依赖:
rust
rfd="0.12.1"
然后在main.rs中导入:
rust
use rfd::FileDialog;
需要注意的是,FileDialog.pickfile()函数返回的是一个枚举类型Option,里面的数据就是文件的路径。
所以,我们可以使用Some来返回此路径。
rust
if let Some(file)=FileDialog::new()
.set_directory("/")
.add_filter("all", &["*"]) //添加文件过滤,all是显示所有类型
.add_filter("文本文件(*txt)", &["txt", "rs"]) //只显示文本类型
.add_filter("图像文件(*png*jpg*bmp)", &["png","jpg","jpeg","bmp"]) //只显示图像类型
.set_title("打开图像")
.pick_file()
{
self.iamgepath=file.display().to_string();
};
这样我们打开的图像的路径,就赋给了self.imagepath。
二 将图片显示在窗口界面上
我们现在已经得到了图像的路径,那么我们如何将图像显示在窗口上呢?这里需要用到iced提供的image这个功能,它是被定义为iced_widget的一个特性,即Features。Features是Rust中的一个概念,或者是一种机制。以下是rust官方手册关于Features的概念,大家自己理解一下。
- Cargo "features" provide a mechanism to express conditional compilation and optional dependencies.
- A package defines a set of named features in the features table of Cargo.toml, and each feature can either be enabled or disabled. Features for the package being built can be enabled on the command-line with flags such as --features. Features for dependencies can be enabled in the dependency declaration in Cargo.toml.
本篇说明一下如何使用image这个Features,在你的项目的Cargo.toml文件中,添加了iced依赖后,添加以下语句:
rust
iced.features=["image"]
然后可以在main.rs中导入image:
rust
use iced::widget::{text, button,slider,column,image,container};
另外,我们在本系列第二篇提到过一个第三方的图像库Image,实际上iced中处理图像也用到了这个库,所以我们将Image也添加到依赖中:
rust
image="0.24.7"
为了不混乱iced的image和第三方image,我们在导入第三方image时,如下:
rust
extern crate image as img_image;
当然,as后面的名字,你可以自己随便定义,只要你知道它是用来代替第三方image的"命名空间"即可。
image部件显示图像代码:
rust
image(hd).content_fit(ContentFit::Fill),
此处,image函数的参数是一个Handle,官方关于image的源代码:
rust
/// Creates a new [`Image`].
///
/// [`Image`]: widget::Image
#[cfg(feature = "image")]
pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> {
crate::Image::new(handle.into())
}
所以,我们使用时,需要将图像文件转为Handle类型:
rust
let hd= if cfg!(target_arch = "wasm32") { //Wasm32是一种基于WebAssembly(Wasm)的32位虚拟机
image::Handle::from_path("iced_test/src/img1.png")
} else {
//image::Handle::from_path("../iced_test/src/img2.jpeg")
image::Handle::from_path(img_path)
};
如上,使用image-Handle-from_path函数,从图像路径获取image的Handle,然后将此Handle传给image部件即可。
完整代码:
rust
use iced::widget::{text, button,slider,column,row,image,container};
use iced::{Alignment, Element, Length,Sandbox, Settings, ContentFit, alignment};
use iced::window;
use iced::window::icon;
use iced::window::Position;
use iced::Font;
use iced::font::Family;
extern crate image as img_image;
extern crate num_complex;
use rfd::FileDialog;
pub fn main() ->iced::Result{
//Counter::run(Settings::default())
let ff="微软雅黑";
//第二种获取rgba图片的方法,利用Image库
let img2=img_image::open("../iced_test/src/dota22.png");
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(), 64, 64);
let ico2_file=match ico2{
Ok(file)=>file,
Err(error)=>panic!("error is {}",error),
};
Counter::run(Settings {
window:window::Settings{ //设置窗口尺寸和位置及图标
size:(800,600),
position:Position::Specific(100, 40),
icon:Some(ico2_file),
..window::Settings::default()
},
default_font:Font{ //设置UI界面的显示字体
family:Family::Name(ff),
..Font::DEFAULT},
..Settings::default()
})
}
pub struct Counter{
srcimgpath:String,
destimgpath:String,
slivalue:f32,
}
#[derive(Debug, Clone,Copy)]
pub enum Message {
OpenimgPressed,
SaveimgPressed,
SliderChanged(f32),
}
impl Sandbox for Counter {
type Message = Message;
fn new() -> Self {
let path=String::new();
Self { srcimgpath: path.to_string(), //to_string()类似于clone
destimgpath:path.to_string(),
slivalue:0.0}
}
fn title(&self) -> String {
String::from("iced_UI演示")
}
fn update(&mut self, message: Message) {
match message {
Message::OpenimgPressed => {
if let Some(file)=FileDialog::new()
.set_directory("D:\\008 rustpro\\iced_test\\src")
.add_filter("all", &["*"]) //添加文件过滤,all是显示所有类型
.add_filter("文本文件(*txt)", &["txt", "rs"]) //只显示文本类型
.add_filter("图像文件(*png*jpg*bmp)", &["png","jpg","jpeg","bmp"]) //只显示图像类型
.set_title("打开图像")
.pick_file()
{
self.srcimgpath=file.display().to_string();
};
//println!("{:?}",file);
}
Message::SaveimgPressed=> {
self.destimgpath="".to_string();
}
Message::SliderChanged(vl)=>{
self.slivalue=vl;
}
}
}
fn view(&self) -> Element<Message> {
let img_path=&self.srcimgpath;
let hd= if cfg!(target_arch = "wasm32") { //Wasm32是一种基于WebAssembly(Wasm)的32位虚拟机
image::Handle::from_path("iced_test/src/img1.png")
} else {
//image::Handle::from_path("../iced_test/src/img2.jpeg")
image::Handle::from_path(img_path)
};
// let hd2= if cfg!(target_arch = "wasm32") { //Wasm32是一种基于WebAssembly(Wasm)的32位虚拟机
// image::Handle::from_path("iced_test/src/img1.png")
// } else {
// image::Handle::from_path(img_path)
// };
//println!("hd is :{:?}",hd);
container(
column![
row![
//btn1
button(text("打开图像")
.horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center)
.size(15)
).on_press(Message::OpenimgPressed)
.padding(4),
//btn2
button(text("保存图像")
.horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center)
.size(15)
).on_press(Message::SaveimgPressed)
.padding(4),
].spacing(10).padding(10)
.align_items(Alignment::Start),
//text:source image path
text(format!("原图像路径:{:?}",self.srcimgpath)).size(15)
.horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center),
row![
text("图像尺寸调整:").size(15),
//slider
slider(0.0..=100.0, self.slivalue, Message::SliderChanged).step(0.01).width(200),
].spacing(20),
//text:dest image path
text(&self.destimgpath).size(15),
row![
image(hd).content_fit(ContentFit::Fill),
//image(hd2).width(Length::Fixed(100.0)).height(Length::Fixed(100.0)).content_fit(ContentFit::Fill)
].spacing(10)
.padding(10)
]
.spacing(10)
.padding(30)
.align_items(Alignment::Start)
)
.into()
}
}
以上代码中,不仅包含本篇涉及的内容,也包含前2篇中涉及的内容。
动态演示图:
