目录
- 简介
- 简单示例
- 创建项目
- 界面设计
- 切换主题
- 自定义字体
- 自定义图标
- 经典布局
- 定义导航变量
- 实现导航界面
- 实现导航逻辑
- 实现主框架布局
- 调试运行
简介
egui(发音为“e-gooey”)是一个简单、快速且高度可移植的 Rust 即时模式 GUI 库,跨平台、Rust原生,适合一些小工具和游戏引擎GUI:
文档:https://docs.rs/egui/latest/egui/
演示:https://www.egui.rs/#demo
github:https://github.com/emilk/egui
关于即时模式GUI,可以参考 使用C++界面框架ImGUI开发一个简单程序 里面的介绍,ImGUI是C++的一个即时模式GUI库。
简单示例
创建项目
首先使用cargo工具快速构建项目:
cargo new eguitest
然后添加依赖:
cargo add eframe
egui只是一个图形库,而不是图形界面开发框架,eframe是与egui配套使用的图形框架。
为了静态插入图片,还需要增加egui_extras依赖:
cargo add egui_extras
然后在Cargo.toml文件中编辑features:
egui_extras = { version = "0.26.2", features = ["all_loaders"] }
界面设计
打开src/main.rc,编写第一个eframe示例程序:
//隐藏Windows上的控制台窗口 #![windows_subsystem = "windows"] use eframe::egui; fn main() -> Result<(), eframe::Error> { // 创建视口选项,设置视口的内部大小为320x240像素 let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; // 运行egui应用程序 eframe::run_native( "My egui App", // 应用程序的标题 options, // 视口选项 Box::new(|cc| { // 为我们提供图像支持 egui_extras::install_image_loaders(&cc.egui_ctx); // 创建并返回一个实现了eframe::App trait的对象 Box::new(MyApp::new(cc)) }), ) } //定义 MyApp 结构体 struct MyApp { name: String, age: u32, } //MyApp 结构体 new 函数 impl MyApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { // 结构体赋初值 Self { name: "Arthur".to_owned(), age: 42, } } } //实现 eframe::App trait impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // 在中央面板上显示egui界面 egui::CentralPanel::default().show(ctx, |ui| { // 显示标题 ui.heading("My egui Application"); // 创建一个水平布局 ui.horizontal(|ui| { // 显示姓名标签 let name_label = ui.label("Your name: "); // 显示姓名输入框(单行文本框) ui.text_edit_singleline(&mut self.name) .labelled_by(name_label.id); // 关联标签 }); // 显示年龄滑块 ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); if ui.button("Increment").clicked() { // 点击按钮后将年龄加1 self.age += 1; } // 显示问候语 ui.label(format!("Hello '{}', age {}", self.name, self.age)); // 显示图片,图片放在main.rs的同级目录下(可以自定义到其它目录) ui.image(egui::include_image!("ferris.png")); }); } }
运行结果如下:
切换主题
egui提供了明亮、暗黄两种主题,在APP结构体上添加 theme_switcher 方法:
impl MyApp { // 切换主题 fn theme_switcher(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { ui.horizontal(|ui| { if ui.button("Dark").clicked() { ctx.set_visuals(egui::Visuals::dark()); } if ui.button("Light").clicked() { ctx.set_visuals(egui::Visuals::light()); } }); } }
然后在update函数中调用:
egui::CentralPanel::default().show(ctx, |ui| { //... // 切换主题 self.theme_switcher(ui, ctx); // 显示图片 ui.image(egui::include_image!("ferris.png")); });
egui的Style结构体可以自定义主题,不过一般默认主题就够用了。
自定义字体
egui默认不支持中文,实现一个 setup_custom_fonts 函数:
//自定义字体 fn setup_custom_fonts(ctx: &egui::Context) { // 创建一个默认的字体定义对象 let mut fonts = egui::FontDefinitions::default(); //安装的字体支持.ttf和.otf文件 //文件放在main.rs的同级目录下(可以自定义到其它目录) fonts.font_data.insert( "my_font".to_owned(), egui::FontData::from_static(include_bytes!( "msyh.ttc" )), ); // 将字体添加到 Proportional 字体族的第一个位置 fonts .families .entry(egui::FontFamily::Proportional) .or_default() .insert(0, "my_font".to_owned()); // 将字体添加到 Monospace 字体族的末尾 fonts .families .entry(egui::FontFamily::Monospace) .or_default() .push("my_font".to_owned()); // 将加载的字体设置到 egui 的上下文中 ctx.set_fonts(fonts); }
然后再MyApp结构体的new方法中调用:
//... impl MyApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { //加载自定义字体 setup_custom_fonts(&cc.egui_ctx); //... } } //...
运行结果:
自定义图标
先导入image库,在终端中运行:
cargo add image
还需要导入std::sync::Arc、eframe::egui::IconData ,库引入区如下:
use eframe::egui; use eframe::egui::IconData; use std::sync::Arc; use image;
在main()函数中将native_options的声明改为可变变量的声明,并加入改变图标代码:
fn main() -> Result<(), eframe::Error> { // 创建视口选项,设置视口的内部大小为320x240像素 let mut options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; //导入图标,图片就用上面的 let icon_data = include_bytes!("ferris.png"); let img = image::load_from_memory_with_format(icon_data, image::ImageFormat::Png).unwrap(); let rgba_data = img.into_rgba8(); let (width, height) =(rgba_data.width(),rgba_data.height()); let rgba: Vec<u8> = rgba_data.into_raw(); options.viewport.icon=Some(Arc::<IconData>::new(IconData { rgba, width, height})); // ... }
经典布局
在上面示例的基础上,实现一个上中下或左中右的经典三栏布局,main函数不需要修改,只需要修改MyApp结构体的定义即可。
定义导航变量
先定义一个导航枚举,用来在标记当前要显示的界面:
//导航枚举 enum Page { Test, Settings, }
为了方便理解示例,在 MyApp 中只定义一个 page 字段,并同步修改new函数:
//定义 MyApp 结构体 struct MyApp { page:Page, } //MyApp 结构体 new 函数 impl MyApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { setup_custom_fonts(&cc.egui_ctx); // 结构体赋初值 Self { page:Page::Test, } } }
实现导航界面
在 MyApp 中定义导航栏的界面,
impl MyApp { //左侧导航按钮,egui没有内置树控件,有需要可以自己实现 fn left_ui(&mut self, ui: &mut egui::Ui) { //一个垂直布局的ui,内部控件水平居中并对齐(填充全宽) ui.vertical_centered_justified(|ui| { if ui.button("测试").clicked() { self.page=Page::Test; } if ui.button("设置").clicked() { self.page=Page::Settings; } //根据需要定义其它按钮 }); } //...其它方法 }
实现导航逻辑
在 MyApp 中定义一个 show_page 方法来进行界面调度,每个界面再单独实现自己的UI函数
impl MyApp { //...其它方法 //根据导航显示页面 fn show_page(&mut self, ui: &mut egui::Ui) { match self.page { Page::Test => { self.test_ui(ui); } Page::Settings => { //... } } } //为了方便理解示例这里只显示一张图片 fn test_ui(&mut self, ui: &mut egui::Ui) { ui.image(egui::include_image!("ferris.png")); } //...其它方法 }
实现主框架布局
在 MyApp 中间实现 main_ui 方法,可以根据自己的需要调整各个栏的位置:
impl MyApp { //...其它方法 //主框架布局 fn main_ui(&mut self, ui: &mut egui::Ui) { // 添加面板的顺序非常重要,影响最终的布局 egui::TopBottomPanel::top("top_panel") .resizable(true) .min_height(32.0) .show_inside(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("标题栏"); }); ui.label("标题栏内容"); }); }); egui::SidePanel::left("left_panel") .resizable(true) .default_width(150.0) .width_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("左导航栏"); }); egui::ScrollArea::vertical().show(ui, |ui| { self.left_ui(ui); }); }); egui::SidePanel::right("right_panel") .resizable(true) .default_width(150.0) .width_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("右导航栏"); }); egui::ScrollArea::vertical().show(ui, |ui| { ui.label("右导航栏内容"); }); }); egui::TopBottomPanel::bottom("bottom_panel") .resizable(false) .min_height(0.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("状态栏"); }); ui.vertical_centered(|ui| { ui.label("状态栏内容"); }); }); egui::CentralPanel::default().show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("主面板"); }); egui::ScrollArea::vertical().show(ui, |ui| { ui.label("主面板内容"); self.show_page(ui); }); }); } }
调试运行
在 main 函数中稍微调整一下窗口大小:
// 创建视口选项 let mut options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([1000.0, 500.0]), ..Default::default() };
在 update 函数中调用 main_ui 函数:
impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { //设置主题 ctx.set_visuals(egui::Visuals::dark()); // 在中央面板上显示egui界面 egui::CentralPanel::default().show(ctx, |ui| { self.main_ui(ui); }); } }
运行结果如下:
以上就是Rust中GUI库egui的简单应用指南的详细内容,更多关于Rust egui的资料请关注本网站其它相关文章!
您可能感兴趣的文章:
- Rust 语言的全链路追踪库 tracing使用方法
- 详解thiserror库在Rust中的使用