Rust GUI库 egui 的简单应用

Rust GUI库 egui 的简单应用

目录简介简单示例创建项目界面设计切换主题自定义字体自定义图标经典布局定义导航变量实现导航界面实现导航逻辑实现主框架布局调试运行参考资料

简介

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 = rgba_data.into_raw();

options.viewport.icon=Some(Arc::::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/eframe初探入门(〇):生成第一个界面

Rust GUI库egui/eframe初探入门(二):更换图标和字体,实现中文界面

相关推荐

365betapp投注 普洱茶适合什么季节喝?普洱茶什么时候喝好

普洱茶适合什么季节喝?普洱茶什么时候喝好

📅 07-14 👁️ 5672
365bet体育在线总站 《梦幻西游2》15门派

《梦幻西游2》15门派

📅 09-03 👁️ 5590
365结束投注什么意思 快手同城位置怎么切换?快手切换同城位置详细教程

快手同城位置怎么切换?快手切换同城位置详细教程

📅 07-21 👁️ 2132