前言
本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析,主要讲解egui的源代码、部件属性、如何应用。
环境配置
系统:windows
平台:visual studio code
语言:rust
库:egui、eframe
概述
本文是本专栏的第二篇博文,主要讲述按钮button、标签label部件的使用。
事实上,类似于iced,egui都提供了示例程序,本专栏的博文都是建立在官方示例程序以及源代码的基础上,进行的实例讲解。
即,本专栏的文章并非只是简单的翻译egui的官方示例与文档,而是针对于官方代码进行的实际使用,会在官方的代码上进行修改,包括解决一些问题。
系列博文链接:
1、<Rust>egui部件学习:如何在窗口及部件显示中文字符?
基于第一篇文章,我们将设置自定义字体的函数写到单独的mod里:
名称可以自己起,如setfont,然后我们在main中调用函数即可。具体函数代码和之前是一样的,就不重复贴了,但是这里稍作了修改,将字体的字节数组作为参数传给函数:
pub fn setup_custom_fonts(ctx: &egui::Context,fontbyte:&'static [u8])
并且为了能在其他mod里调用,函数前面添加了pub关键词。
在第一篇中,我们只是介绍了如何显示中文字符,本文我们会说明窗口的显示,以及如何添加按钮、标签部件。
部件属性
窗口显示
先来看窗口的显示,不同于iced库。egui的部件显示和更新都放在了update函数里。而iced的部件显示放在view函数,响应则放在update中。
官方给出的典型的egui代码如下:
use eframe::egui;
fn main() {
let native_options = eframe::NativeOptions::default();
eframe::run_native("My egui App", native_options, Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))));
}
#[derive(Default)]
struct MyEguiApp {}
impl MyEguiApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
// Restore app state using cc.storage (requires the "persistence" feature).
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
// for e.g. egui::PaintCallback.
Self::default()
}
}
impl eframe::App for MyEguiApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Hello World!");
});
}
}
和iced是有些类似的,我们创建一个结构体,里面是我们要操作的数据,然后我们为其实现eframe的app特性,窗口建立后,结构体数据就可以通过app的update来更新了。
不同于iced,iced中application是包含了new函数的,但是egui的app中,并没有直接添加new,而是需要单独实现:
impl MyApp{
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setfont::setup_custom_fonts(&cc.egui_ctx,MY_FONTS_BYTES);
Self {
show_confirmation_dialog:false,
allowed_to_close:false,
}
}
}
我们设置自定义字体,就是在new函数中初始化的。
按钮部件button
窗口可以显示了,但是运行后只是一个空的窗体,我们需要在其中添加按钮等部件,以实现和窗口的交互。
egui中,部件的添加,都是在show函数里:
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("尝试关闭窗口");
ui.button("按钮1")
});
如上,show有两个参数,ctx可以设置UI的格式,而add_contents用于添加其他部件:
ui.button("按钮1")
这种方式是快捷的调用,它返回的是一个Response,即交互数据,我们可以使用Response来进行逻辑处理。比如,按钮点击响应,可以这样设置:
let btn_res=ui.button("按钮1");
if btn_res.clicked(){
println!("按钮1点击")
}else {
}
根据egui的介绍,egui是即时模式,作者在github解释了这个问题:
所以,根据作者的说明,egui是很适合集成到游戏开发中的,如果你要了解更多,可以参考作者给出的说明:
https://docs.rs/egui/latest/egui/#understanding-immediate-mode
总的来说,egui并不需要存储部件以供使用,而是即时刷新,所以,egui的缺点是布局不是很方便,因为是即时刷新,所以需要提前知道布局。而且,有些场景下,可能还会有很大的问题,这是一个需要注意的点。
好了,我们现在回到本文的内容上,我们在update中添加了按钮后,我们来运行看一下:
当我们点击按钮后,会在控制台打印文本:
标签部件label
现在我们在窗口添加标签,点击按钮,将文本显示在标签上。
let btn_res=ui.button("按钮1");
if btn_res.clicked(){
//println!("按钮1点击")
self.lbltext="按钮1点击".to_string();
}else {
}
ui.label(format!("{}",self.lbltext))
如上,我们添加了label,其文本内容是一个格式化的参数:
format!("{}",self.lbltext)
这样设置是为了文本可以动态变化,其中self.lbltext是我们在结构体中创建的元素:
#[derive(Default)]
struct MyApp {
show_confirmation_dialog: bool,
allowed_to_close: bool,
lbltext:String,
}
这样,运行后,我们点击按钮,就可以在标签上显示内容:
当然,我们也可以是文本框输入部件,来动态输入文本,来查看标签内容变化,不够,文本输入我们将在下一个章节里介绍,本文就不在赘述了。
完整代码
main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::egui;
mod setfont;
const MY_FONTS_BYTES:&[u8]=include_bytes!("../font/simsun.ttf");
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
eframe::run_native(
"egui测试窗口",
options,
//Box::new(|_cc| Ok(Box::<MyApp>::default())),
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
}
#[derive(Default)]
struct MyApp {
show_confirmation_dialog: bool,
allowed_to_close: bool,
lbltext:String,
}
impl MyApp{
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setfont::setup_custom_fonts(&cc.egui_ctx,MY_FONTS_BYTES);
Self {
show_confirmation_dialog:false,
allowed_to_close:false,
lbltext:"no text".to_string(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("尝试关闭窗口");
let btn_res=ui.button("按钮1");
if btn_res.clicked(){
//println!("按钮1点击")
self.lbltext="按钮1点击".to_string();
}else {
}
ui.text_edit_singleline(&mut self.lbltext);
ui.label(format!("{}",self.lbltext))
});
// if ctx.input(|i| i.viewport().close_requested()) {
// if self.allowed_to_close {
// // do nothing - we will close
// } else {
// ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose);
// self.show_confirmation_dialog = true;
// }
// }
if self.show_confirmation_dialog {
egui::Window::new("你想要关闭吗?")
.collapsible(false)
.resizable(false)
.show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("否").clicked() {
self.show_confirmation_dialog = false;
self.allowed_to_close = false;
}
if ui.button("是").clicked() {
self.show_confirmation_dialog = false;
self.allowed_to_close = true;
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
});
});
}
}
}