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篇中涉及的内容。

动态演示图:

相关推荐
2601_962885725 小时前
如何用 Python 自动识别股票的支撑位与压力位?
开发语言·python
爱吃奥利奥_wen5 小时前
面向对象三板斧:封装、继承、多态,到底在“装“什么、“承“什么、“变“什么?
开发语言·经验分享·c#
我可能是个假开发5 小时前
FTP Unicode 文件名导致 `550` 的问题分析与解决方案
java·开发语言·spring
WL学习笔记6 小时前
日期类(C++)
开发语言·c++·日期类实现
谢亮_vipxieliang7 小时前
Go map与结构体的正确使用
开发语言·golang·哈希算法
H.莓飛7 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
xxwl5857 小时前
数据结构知识点和代码实现总结(C语言实现)
c语言·开发语言·数据结构
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
code2cat10 小时前
Java进阶篇之AtomicMarkableReference:引用还在,标记已经改变
java·开发语言·cas·并发编程·逻辑删除
CappuccinoRose10 小时前
FormData数据处理
开发语言·前端·javascript·表单数据