Rust UI开发(三):iced如何打开图片(对话框)并在窗口显示图片?

注:此文适合于对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篇中涉及的内容。

动态演示图:

相关推荐
人间凡尔赛9 小时前
Next.js 16 生产级实战:Cache Components + View Transitions 完整指南
开发语言·javascript·ecmascript
稚南城才子,乌衣巷风流13 小时前
函数:编程中的核心概念
开发语言·前端·javascript
阿米亚波13 小时前
【C++】流式数据输入处理(不完全整理)
开发语言·c++·笔记
大模型码小白13 小时前
JAVA 集合框架进阶:List 与 Set 的深度解析与实战
java·开发语言·人工智能·windows·语言模型·list·ai编程
皓月斯语14 小时前
【C++基础】三目运算符
开发语言·数据结构·c++
初願致夕霞15 小时前
C++懒汉单例设计详解
服务器·开发语言·c++
脱胎换骨-军哥15 小时前
C++分布式系统设计:从通信引擎到分布式共识
开发语言·c++·分布式
脱胎换骨-军哥16 小时前
C++零成本抽象理论深度拆解:现代C++如何在不牺牲性能的前提下提供高级语法封装
java·开发语言·c++
lbb 小魔仙16 小时前
VS Code Python 高级调试技巧:从入门到精通
开发语言·python
方便面不加香菜16 小时前
C++ 模板进阶
开发语言·c++