经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Rust » 查看文章
Rust GUI库 egui 的简单应用
来源:cnblogs  作者:二次元攻城狮  时间:2024/3/15 9:06:15  对本文有异议

简介

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库。

image

简单示例

创建项目

首先使用cargo工具快速构建项目:

  1. cargo new eguitest

然后添加依赖:

  1. cargo add eframe

egui只是一个图形库,而不是图形界面开发框架,eframe是与egui配套使用的图形框架

为了静态插入图片,还需要增加egui_extras依赖:

  1. cargo add egui_extras

然后在Cargo.toml文件中编辑features

  1. egui_extras = { version = "0.26.2", features = ["all_loaders"] }

界面设计

打开src/main.rc,编写第一个eframe示例程序:

  1. //隐藏Windows上的控制台窗口
  2. #![windows_subsystem = "windows"]
  3. use eframe::egui;
  4. fn main() -> Result<(), eframe::Error> {
  5. // 创建视口选项,设置视口的内部大小为320x240像素
  6. let options = eframe::NativeOptions {
  7. viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
  8. ..Default::default()
  9. };
  10. // 运行egui应用程序
  11. eframe::run_native(
  12. "My egui App", // 应用程序的标题
  13. options, // 视口选项
  14. Box::new(|cc| {
  15. // 为我们提供图像支持
  16. egui_extras::install_image_loaders(&cc.egui_ctx);
  17. // 创建并返回一个实现了eframe::App trait的对象
  18. Box::new(MyApp::new(cc))
  19. }),
  20. )
  21. }
  22. //定义 MyApp 结构体
  23. struct MyApp {
  24. name: String,
  25. age: u32,
  26. }
  27. //MyApp 结构体 new 函数
  28. impl MyApp {
  29. fn new(cc: &eframe::CreationContext<'_>) -> Self {
  30. // 结构体赋初值
  31. Self {
  32. name: "Arthur".to_owned(),
  33. age: 42,
  34. }
  35. }
  36. }
  37. //实现 eframe::App trait
  38. impl eframe::App for MyApp {
  39. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
  40. // 在中央面板上显示egui界面
  41. egui::CentralPanel::default().show(ctx, |ui| {
  42. // 显示标题
  43. ui.heading("My egui Application");
  44. // 创建一个水平布局
  45. ui.horizontal(|ui| {
  46. // 显示姓名标签
  47. let name_label = ui.label("Your name: ");
  48. // 显示姓名输入框(单行文本框)
  49. ui.text_edit_singleline(&mut self.name)
  50. .labelled_by(name_label.id); // 关联标签
  51. });
  52. // 显示年龄滑块
  53. ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
  54. if ui.button("Increment").clicked() {
  55. // 点击按钮后将年龄加1
  56. self.age += 1;
  57. }
  58. // 显示问候语
  59. ui.label(format!("Hello '{}', age {}", self.name, self.age));
  60. // 显示图片,图片放在main.rs的同级目录下(可以自定义到其它目录)
  61. ui.image(egui::include_image!("ferris.png"));
  62. });
  63. }
  64. }

运行结果如下:
image

切换主题

egui提供了明亮、暗黄两种主题,在APP结构体上添加 theme_switcher 方法:

  1. impl MyApp {
  2. // 切换主题
  3. fn theme_switcher(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
  4. ui.horizontal(|ui| {
  5. if ui.button("Dark").clicked() {
  6. ctx.set_visuals(egui::Visuals::dark());
  7. }
  8. if ui.button("Light").clicked() {
  9. ctx.set_visuals(egui::Visuals::light());
  10. }
  11. });
  12. }
  13. }

然后在update函数中调用:

  1. egui::CentralPanel::default().show(ctx, |ui| {
  2. //...
  3. // 切换主题
  4. self.theme_switcher(ui, ctx);
  5. // 显示图片
  6. ui.image(egui::include_image!("ferris.png"));
  7. });

egui的Style结构体可以自定义主题,不过一般默认主题就够用了。

自定义字体

egui默认不支持中文,实现一个 setup_custom_fonts 函数:

  1. //自定义字体
  2. fn setup_custom_fonts(ctx: &egui::Context) {
  3. // 创建一个默认的字体定义对象
  4. let mut fonts = egui::FontDefinitions::default();
  5. //安装的字体支持.ttf和.otf文件
  6. //文件放在main.rs的同级目录下(可以自定义到其它目录)
  7. fonts.font_data.insert(
  8. "my_font".to_owned(),
  9. egui::FontData::from_static(include_bytes!(
  10. "msyh.ttc"
  11. )),
  12. );
  13. // 将字体添加到 Proportional 字体族的第一个位置
  14. fonts
  15. .families
  16. .entry(egui::FontFamily::Proportional)
  17. .or_default()
  18. .insert(0, "my_font".to_owned());
  19. // 将字体添加到 Monospace 字体族的末尾
  20. fonts
  21. .families
  22. .entry(egui::FontFamily::Monospace)
  23. .or_default()
  24. .push("my_font".to_owned());
  25. // 将加载的字体设置到 egui 的上下文中
  26. ctx.set_fonts(fonts);
  27. }

然后再MyApp结构体的new方法中调用:

  1. //...
  2. impl MyApp {
  3. fn new(cc: &eframe::CreationContext<'_>) -> Self {
  4. //加载自定义字体
  5. setup_custom_fonts(&cc.egui_ctx);
  6. //...
  7. }
  8. }
  9. //...

运行结果:
image

自定义图标

先导入image库,在终端中运行:

  1. cargo add image

还需要导入std::sync::Arc、eframe::egui::IconData ,库引入区如下:

  1. use eframe::egui;
  2. use eframe::egui::IconData;
  3. use std::sync::Arc;
  4. use image;

在main()函数中将native_options的声明改为可变变量的声明,并加入改变图标代码:

  1. fn main() -> Result<(), eframe::Error> {
  2. // 创建视口选项,设置视口的内部大小为320x240像素
  3. let mut options = eframe::NativeOptions {
  4. viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
  5. ..Default::default()
  6. };
  7. //导入图标,图片就用上面的
  8. let icon_data = include_bytes!("ferris.png");
  9. let img = image::load_from_memory_with_format(icon_data, image::ImageFormat::Png).unwrap();
  10. let rgba_data = img.into_rgba8();
  11. let (width, height) =(rgba_data.width(),rgba_data.height());
  12. let rgba: Vec<u8> = rgba_data.into_raw();
  13. options.viewport.icon=Some(Arc::<IconData>::new(IconData { rgba, width, height}));
  14. // ...
  15. }

经典布局

在上面示例的基础上,实现一个上中下或左中右的经典三栏布局,main函数不需要修改,只需要修改MyApp结构体的定义即可。

定义导航变量

先定义一个导航枚举,用来在标记当前要显示的界面:

  1. //导航枚举
  2. enum Page {
  3. Test,
  4. Settings,
  5. }

为了方便理解示例,在 MyApp 中只定义一个 page 字段,并同步修改new函数:

  1. //定义 MyApp 结构体
  2. struct MyApp {
  3. page:Page,
  4. }
  5. //MyApp 结构体 new 函数
  6. impl MyApp {
  7. fn new(cc: &eframe::CreationContext<'_>) -> Self {
  8. setup_custom_fonts(&cc.egui_ctx);
  9. // 结构体赋初值
  10. Self {
  11. page:Page::Test,
  12. }
  13. }
  14. }

实现导航界面

在 MyApp 中定义导航栏的界面,

  1. impl MyApp {
  2. //左侧导航按钮,egui没有内置树控件,有需要可以自己实现
  3. fn left_ui(&mut self, ui: &mut egui::Ui) {
  4. //一个垂直布局的ui,内部控件水平居中并对齐(填充全宽)
  5. ui.vertical_centered_justified(|ui| {
  6. if ui.button("测试").clicked() {
  7. self.page=Page::Test;
  8. }
  9. if ui.button("设置").clicked() {
  10. self.page=Page::Settings;
  11. }
  12. //根据需要定义其它按钮
  13. });
  14. }
  15. //...其它方法
  16. }

实现导航逻辑

在 MyApp 中定义一个 show_page 方法来进行界面调度,每个界面再单独实现自己的UI函数

  1. impl MyApp {
  2. //...其它方法
  3. //根据导航显示页面
  4. fn show_page(&mut self, ui: &mut egui::Ui) {
  5. match self.page {
  6. Page::Test => {
  7. self.test_ui(ui);
  8. }
  9. Page::Settings => {
  10. //...
  11. }
  12. }
  13. }
  14. //为了方便理解示例这里只显示一张图片
  15. fn test_ui(&mut self, ui: &mut egui::Ui) {
  16. ui.image(egui::include_image!("ferris.png"));
  17. }
  18. //...其它方法
  19. }

实现主框架布局

在 MyApp 中间实现 main_ui 方法,可以根据自己的需要调整各个栏的位置:

  1. impl MyApp {
  2. //...其它方法
  3. //主框架布局
  4. fn main_ui(&mut self, ui: &mut egui::Ui) {
  5. // 添加面板的顺序非常重要,影响最终的布局
  6. egui::TopBottomPanel::top("top_panel")
  7. .resizable(true)
  8. .min_height(32.0)
  9. .show_inside(ui, |ui| {
  10. egui::ScrollArea::vertical().show(ui, |ui| {
  11. ui.vertical_centered(|ui| {
  12. ui.heading("标题栏");
  13. });
  14. ui.label("标题栏内容");
  15. });
  16. });
  17. egui::SidePanel::left("left_panel")
  18. .resizable(true)
  19. .default_width(150.0)
  20. .width_range(80.0..=200.0)
  21. .show_inside(ui, |ui| {
  22. ui.vertical_centered(|ui| {
  23. ui.heading("左导航栏");
  24. });
  25. egui::ScrollArea::vertical().show(ui, |ui| {
  26. self.left_ui(ui);
  27. });
  28. });
  29. egui::SidePanel::right("right_panel")
  30. .resizable(true)
  31. .default_width(150.0)
  32. .width_range(80.0..=200.0)
  33. .show_inside(ui, |ui| {
  34. ui.vertical_centered(|ui| {
  35. ui.heading("右导航栏");
  36. });
  37. egui::ScrollArea::vertical().show(ui, |ui| {
  38. ui.label("右导航栏内容");
  39. });
  40. });
  41. egui::TopBottomPanel::bottom("bottom_panel")
  42. .resizable(false)
  43. .min_height(0.0)
  44. .show_inside(ui, |ui| {
  45. ui.vertical_centered(|ui| {
  46. ui.heading("状态栏");
  47. });
  48. ui.vertical_centered(|ui| {
  49. ui.label("状态栏内容");
  50. });
  51. });
  52. egui::CentralPanel::default().show_inside(ui, |ui| {
  53. ui.vertical_centered(|ui| {
  54. ui.heading("主面板");
  55. });
  56. egui::ScrollArea::vertical().show(ui, |ui| {
  57. ui.label("主面板内容");
  58. self.show_page(ui);
  59. });
  60. });
  61. }
  62. }

调试运行

在 main 函数中稍微调整一下窗口大小:

  1. // 创建视口选项
  2. let mut options = eframe::NativeOptions {
  3. viewport: egui::ViewportBuilder::default().with_inner_size([1000.0, 500.0]),
  4. ..Default::default()
  5. };

在 update 函数中调用 main_ui 函数:

  1. impl eframe::App for MyApp {
  2. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
  3. //设置主题
  4. ctx.set_visuals(egui::Visuals::dark());
  5. // 在中央面板上显示egui界面
  6. egui::CentralPanel::default().show(ctx, |ui| {
  7. self.main_ui(ui);
  8. });
  9. }
  10. }

运行结果如下:
image

参考资料

原文链接:https://www.cnblogs.com/timefiles/p/18070897

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号