[rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(00):iced简单窗口的实现以及在窗口显示中文

前言
本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发。
iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动。
本合集是基于新版本的关于分部件(widget)的使用介绍,包括源代码介绍、实例使用等。

环境配置
系统:window10
平台:visual studio code
语言:rust
库:iced 0.13

iced窗口实现

1、一个典型的iced窗口程序

根据iced库作者的叙述,他是受到了Elm的启发,而创建了iced。
Elm的架构如下:
image
想要详细了解的,可以去Elm的官网看看:
https://elm-lang.org/
本文不关注Elm的具体,而是关注于iced的实现。我们以iced官方举例的一个典型的计数器程序来说明。
iced的结构如下:

  1. State — the state of your application
  2. Messages — user interactions or meaningful events that you care about
  3. View logic — a way to display your state as widgets that may produce messages on user interaction
  4. Update logic — a way to react to messages and update your state

可以看到,与Elm的架构的确是一脉相承。
程序介绍:这个计数器程序,是这样的,有两个按钮button、一个text文本部件,点击按钮,将产生计数,增加或者减少,数值的增、减体现在文本上。
在此处,数值变量,设置为一个State变量,而按钮的点击将产生message,按钮、文本的显示有view来提供,而点击按钮产生消息,则由update来更新。
先来看一个完整代码(与官方源代码稍有差异):

use iced::widget::{button,text,column};

#[derive(Default)]
struct Counter {
    count:i32,
}
#[derive(Debug,Clone, Copy)]
enum Message {
    Inc,
    Dec,
}
fn main() ->iced::Result {
    
    iced::run("counter", Counter::update, Counter::view)
}

impl Counter {
    pub fn update(&mut self, message:Message) {
        match message {
            Message::Inc =>
            {
                self.count += 1;
            } 
            Message::Dec => 
            {
                self.count -= 1;
            }
        }
    }
    pub fn view(&self) -> iced::Element<Message> {

        column![
            button("inc").on_press(Message::Inc),
            text(self.count).size(20),
            button("dec").on_press(Message::Dec)
        ].padding(20)
        .align_x(iced::Center)
        .into()
    }  
}

如果你运行程序:cargo run效果如下:
image
我们可以来简单了解一下代码。
首先是要导入需要使用的部件:use iced::widget::{button,text,column};
本例中,我们导入了button、text以及column三个widget,其中column属于布局部件,可以添加子部件,如button、text。
接着,在iced的架构中,我们需要创建State以及Message:

#[derive(Default)]
struct Counter {
    count:i32,
}
#[derive(Debug,Clone, Copy)]
enum Message {
    Inc,
    Dec,
}

可以看到,State是struct类型,而Message是枚举类型,当然本例中是最简单的,我们设置了count为i32整数型变量,而Inc、Dec是枚举的两个元素。
接着,我们需要实现view以及update:

impl Counter {
    pub fn update(&mut self, message:Message) {
        match message {
            Message::Inc =>
            {
                self.count += 1;
            } 
            Message::Dec => 
            {
                self.count -= 1;
            }
        }
    }
    pub fn view(&self) -> iced::Element<Message> {

        column![
            button("inc").on_press(Message::Inc),
            text(self.count).size(20),
            button("dec").on_press(Message::Dec)
        ].padding(20)
        .align_x(iced::Center)
        .into()
    }  
}

此处我们使用impl来为我们创建的State即Counter结构体,实现view和update函数功能,简单看一下这两个函数,先说update,它是用于接收UI界面的反馈,然后对State进行更新的:

match message {
            Message::Inc =>
            {
                self.count += 1;
            } 
            Message::Dec => 
            {
                self.count -= 1;
            }
        }

标准的iced代码中,通过对Message进行匹配,以针对不同的UI产生的消息,运行相应的逻辑,当然此处也可以写的更复杂,后续介绍。
而view函数是用于渲染UI部件,包括初始化以及update后的刷新:

text(self.count).size(20),

以text部件的显示值为例,我们绑定了State中定义的变量count,当count值改变时,view会即时将其渲染到text的value中,以在UI上显示,在本例中,当我们点击按钮后,文本值就会增加或者减少。

最后,在主函数中,调用iced::run()

fn main() ->iced::Result {
    
    iced::run("counter", Counter::update, Counter::view)
}

iced::run返回的是一个Result,所以需要修改main函数的反馈为iced::Result。

以上,我们完成一个典型的简单的iced窗口程序。

2、如何显示中文

我们对上面的代码做一些改变,将按钮的文本修改为中文的“增加”和“减少”:

 	button("增加").on_press(Message::Inc),
	text(self.count).size(20),
 	button("减少").on_press(Message::Dec)

然后再运行程序看一下结果:
image
很清楚的看到,按钮文本显示为乱码,如图所示,并没有正确的显示中文,原因是iced的默认字体是:SansSerif

官方源码
 pub const DEFAULT: Font = Font {
        family: Family::SansSerif,
        weight: Weight::Normal,
        stretch: Stretch::Normal,
        style: Style::Normal,
    };

所以,如果我们要显示中文,需要修改其字体,而字体属于Settings的参数,所以,我们需要设置Settings。

官方源码
/// The settings of an iced program.
#[derive(Debug, Clone)]
pub struct Settings {
    /// The identifier of the application.
    ///
    /// If provided, this identifier may be used to identify the application or
    /// communicate with it through the windowing system.
    pub id: Option<String>,

    /// The fonts to load on boot.
    pub fonts: Vec<Cow<'static, [u8]>>,

    /// The default [`Font`] to be used.
    ///
    /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif).
    pub default_font: Font,

    /// The text size that will be used by default.
    ///
    /// The default value is `16.0`.
    pub default_text_size: Pixels,

    /// If set to true, the renderer will try to perform antialiasing for some
    /// primitives.
    ///
    /// Enabling it can produce a smoother result in some widgets, like the
    /// [`Canvas`], at a performance cost.
    ///
    /// By default, it is disabled.
    ///
    /// [`Canvas`]: crate::widget::Canvas
    pub antialiasing: bool,
}

在之前的主函数中,我们调用了iced::run来运行iced应用,接下来我们需要修改它。
如果我们只是需要修改字体,那么有一个简单的方法:

fn main() ->iced::Result {
    
    //iced::run("counter", Counter::update, Counter::view)  
    iced::application("count",Counter::update,Counter::view)
            .default_font(iced::Font::from(Font::with_name("微软雅黑")))
            .run()

}

如上,我们使用iced::application().run()来调用程序,事实上,iced::run()是一种简写。
然后在基础上直接调用default_font函数,为其传入自定义字体:
iced::Font::from(Font::with_name("微软雅黑"))
注意,此处我们使用了字体名称,这是一种“偷懒”的写法,前提是你的电脑中安装了此字体。运行后效果:
image

如果电脑中没有设置的字体,就会使用默认字体。
但通常来说,像宋体、微软雅黑这些字体都是电脑自带的字体,所以我们可以这样来使用。

3、设置窗口位置、尺寸以及icon图标

我们创建了窗口后,很显然希望能够自定义窗口的大小、初始位置以及图标等,这些都是在iced的application的window属性中设置:

官方源码
/// 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,
}

我们在主函数中修改:

   iced::application("count",Counter::update,Counter::view)
            .default_font(iced::Font::with_name("微软雅黑"))
            .window(Settings{
                size:iced::Size{
                    width:300.0,
                    height:300.0
                },
                position:Position::Specific(iced::Point{
                    x:100.0,
                    y:100.0
                }),
                icon:Some(),
                ..Default::default()
            })
            .run()

上述代码中,我们设置了window尺寸、位置以及添加了icon,但是注意到,我们没有为icon设置具体的图片,此处我们需要单独来说一下,iced中的icon,其参数类型为 Option < Icon >:

官方源码
/// An window icon normally used for the titlebar or taskbar.
#[derive(Debug, Clone)]
pub struct Icon {
    rgba: Vec<u8>,
    size: Size<u32>,
}

Icon是一个包含图像数据的u8数组,我们需要提供这样的图片,或者使用其他图片处理库进行转换,所以这里我们要引入image库。
在toml中添加引用:
image="0.25.5"
然后在程序中调用:
extern crate image as eximg;
这里为什么要这样调用?是因为iced中也有一个image的feature,用于处理图片,所以,虽然我们暂时未使用iced中image,但是为了以后调用时二者不至于冲突,所以对于外部导入的image,我们使用别名,本例中为eximg。
我们新建一个函数,用于处理图片,即将普通格式如png、jpg等格式的图片,转为iced中的Icon格式,这个函数我们就命名为:img_to_icon

fn img_to_icon(path:&str,w:u32,h:u32) ->Icon{
    let img_dyn=eximg::open(path).expect("open fail");
   
    let img_rgba=img_dyn.to_rgba8();
    let img_icon=icon::from_rgba(img_rgba.to_vec(), w, h).expect("to icon err");

    return img_icon
}

这个转换的代码比较简单,就是根据传入的图片路径,将其转换为Icon然后返回。
调用函数:
let myicon:Icon=img_to_icon("mainicon.png", 256, 256);
此处的图片是从网上下载的:
image

然后将myicon赋予icon:
icon:Some(myicon),
运行看看:
image
注意窗口左上角的图标,是正确显示的。当然,以上的转换函数并不完美,一些图片格式如svg是不能转换的。
当然,rust中是可以对svg进行处理的,但这个不在本文的讨论范围,会在后续介绍。

4、完整代码

最后,贴上完整的代码:

use iced::widget::{button, column, text};
use iced::window::{Settings,Icon,Position};
use iced_widget::core::window::icon;

extern crate image as eximg;

#[derive(Default)]
struct Counter {
    count:i32,
}
#[derive(Debug,Clone, Copy)]
enum Message {
    Inc,
    Dec,
}
fn main() ->iced::Result {
    
    //iced::run("counter", Counter::update, Counter::view) 
    let myicon:Icon=img_to_icon("mainicon.png", 256, 256);
    iced::application("count",Counter::update,Counter::view)
            .default_font(iced::Font::with_name("微软雅黑"))
            .window(Settings{
                size:iced::Size{
                    width:300.0,
                    height:300.0
                },
                position:Position::Specific(iced::Point{
                    x:100.0,
                    y:100.0
                }),
                icon:Some(myicon),
                ..Default::default()
            })
            .run()

}

impl Counter {
    pub fn update(&mut self, message:Message) {
        match message {
            Message::Inc =>
            {
                self.count += 1;
            } 
            Message::Dec => 
            {
                self.count -= 1;
            }
        }
    }
    pub fn view(&self) -> iced::Element<Message> {

        column![
            button("增加Inc").on_press(Message::Inc),
            text(self.count).size(20),
            button("减少Dec").on_press(Message::Dec)
        ].padding(20)
        .align_x(iced::Center)
        .into()
    }  
}

fn img_to_icon(path:&str,w:u32,h:u32) ->Icon{
    let img_dyn=eximg::open(path).expect("open fail");
   
    let img_rgba=img_dyn.to_rgba8();
    let img_icon=icon::from_rgba(img_rgba.to_vec(), w, h).expect("to icon err");

    return img_icon
}
posted @   rongjv  阅读(245)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇
点击右上角即可分享
微信分享提示