前言
本文是关于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的架构如下:
想要详细了解的,可以去Elm的官网看看:
https://elm-lang.org/
本文不关注Elm的具体,而是关注于iced的实现。我们以iced官方举例的一个典型的计数器程序来说明。
iced的结构如下:
- State — the state of your application
- Messages — user interactions or meaningful events that you care about
- View logic — a way to display your state as widgets that may produce messages on user interaction
- 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
效果如下:
我们可以来简单了解一下代码。
首先是要导入需要使用的部件: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)
然后再运行程序看一下结果:
很清楚的看到,按钮文本显示为乱码,如图所示,并没有正确的显示中文,原因是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::Canvaspub 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("微软雅黑"))
注意,此处我们使用了字体名称,这是一种“偷懒”的写法,前提是你的电脑中安装了此字体。运行后效果:
如果电脑中没有设置的字体,就会使用默认字体。
但通常来说,像宋体、微软雅黑这些字体都是电脑自带的字体,所以我们可以这样来使用。
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);
此处的图片是从网上下载的:
然后将myicon赋予icon:
icon:Some(myicon),
运行看看:
注意窗口左上角的图标,是正确显示的。当然,以上的转换函数并不完美,一些图片格式如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
}