Rust中GUI庫(kù)egui的簡(jiǎn)單應(yīng)用指南
簡(jiǎn)介
egui(發(fā)音為“e-gooey”)是一個(gè)簡(jiǎn)單、快速且高度可移植的 Rust 即時(shí)模式 GUI 庫(kù),跨平臺(tái)、Rust原生,適合一些小工具和游戲引擎GUI:
文檔:https://docs.rs/egui/latest/egui/
github:https://github.com/emilk/egui
關(guān)于即時(shí)模式GUI,可以參考 使用C++界面框架ImGUI開(kāi)發(fā)一個(gè)簡(jiǎn)單程序 里面的介紹,ImGUI是C++的一個(gè)即時(shí)模式GUI庫(kù)。

簡(jiǎn)單示例
創(chuàng)建項(xiàng)目
首先使用cargo工具快速構(gòu)建項(xiàng)目:
cargo new eguitest
然后添加依賴(lài):
cargo add eframe
egui只是一個(gè)圖形庫(kù),而不是圖形界面開(kāi)發(fā)框架,eframe是與egui配套使用的圖形框架。
為了靜態(tài)插入圖片,還需要增加egui_extras依賴(lài):
cargo add egui_extras
然后在Cargo.toml文件中編輯features:
egui_extras = { version = "0.26.2", features = ["all_loaders"] }
界面設(shè)計(jì)
打開(kāi)src/main.rc,編寫(xiě)第一個(gè)eframe示例程序:
//隱藏Windows上的控制臺(tái)窗口
#![windows_subsystem = "windows"]
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
// 創(chuàng)建視口選項(xiàng),設(shè)置視口的內(nèi)部大小為320x240像素
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
// 運(yùn)行egui應(yīng)用程序
eframe::run_native(
"My egui App", // 應(yīng)用程序的標(biāo)題
options, // 視口選項(xiàng)
Box::new(|cc| {
// 為我們提供圖像支持
egui_extras::install_image_loaders(&cc.egui_ctx);
// 創(chuàng)建并返回一個(gè)實(shí)現(xiàn)了eframe::App trait的對(duì)象
Box::new(MyApp::new(cc))
}),
)
}
//定義 MyApp 結(jié)構(gòu)體
struct MyApp {
name: String,
age: u32,
}
//MyApp 結(jié)構(gòu)體 new 函數(shù)
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
// 結(jié)構(gòu)體賦初值
Self {
name: "Arthur".to_owned(),
age: 42,
}
}
}
//實(shí)現(xiàn) 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| {
// 顯示標(biāo)題
ui.heading("My egui Application");
// 創(chuàng)建一個(gè)水平布局
ui.horizontal(|ui| {
// 顯示姓名標(biāo)簽
let name_label = ui.label("Your name: ");
// 顯示姓名輸入框(單行文本框)
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id); // 關(guān)聯(lián)標(biāo)簽
});
// 顯示年齡滑塊
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Increment").clicked() {
// 點(diǎn)擊按鈕后將年齡加1
self.age += 1;
}
// 顯示問(wèn)候語(yǔ)
ui.label(format!("Hello '{}', age {}", self.name, self.age));
// 顯示圖片,圖片放在main.rs的同級(jí)目錄下(可以自定義到其它目錄)
ui.image(egui::include_image!("ferris.png"));
});
}
}
運(yùn)行結(jié)果如下:

切換主題
egui提供了明亮、暗黃兩種主題,在APP結(jié)構(gòu)體上添加 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函數(shù)中調(diào)用:
egui::CentralPanel::default().show(ctx, |ui| {
//...
// 切換主題
self.theme_switcher(ui, ctx);
// 顯示圖片
ui.image(egui::include_image!("ferris.png"));
});
egui的Style結(jié)構(gòu)體可以自定義主題,不過(guò)一般默認(rèn)主題就夠用了。
自定義字體
egui默認(rèn)不支持中文,實(shí)現(xiàn)一個(gè) setup_custom_fonts 函數(shù):
//自定義字體
fn setup_custom_fonts(ctx: &egui::Context) {
// 創(chuàng)建一個(gè)默認(rèn)的字體定義對(duì)象
let mut fonts = egui::FontDefinitions::default();
//安裝的字體支持.ttf和.otf文件
//文件放在main.rs的同級(jí)目錄下(可以自定義到其它目錄)
fonts.font_data.insert(
"my_font".to_owned(),
egui::FontData::from_static(include_bytes!(
"msyh.ttc"
)),
);
// 將字體添加到 Proportional 字體族的第一個(gè)位置
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());
// 將加載的字體設(shè)置到 egui 的上下文中
ctx.set_fonts(fonts);
}
然后再M(fèi)yApp結(jié)構(gòu)體的new方法中調(diào)用:
//...
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
//加載自定義字體
setup_custom_fonts(&cc.egui_ctx);
//...
}
}
//...
運(yùn)行結(jié)果:

自定義圖標(biāo)
先導(dǎo)入image庫(kù),在終端中運(yùn)行:
cargo add image
還需要導(dǎo)入std::sync::Arc、eframe::egui::IconData ,庫(kù)引入?yún)^(qū)如下:
use eframe::egui; use eframe::egui::IconData; use std::sync::Arc; use image;
在main()函數(shù)中將native_options的聲明改為可變變量的聲明,并加入改變圖標(biāo)代碼:
fn main() -> Result<(), eframe::Error> {
// 創(chuàng)建視口選項(xiàng),設(shè)置視口的內(nèi)部大小為320x240像素
let mut options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
//導(dǎo)入圖標(biāo),圖片就用上面的
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}));
// ...
}
經(jīng)典布局
在上面示例的基礎(chǔ)上,實(shí)現(xiàn)一個(gè)上中下或左中右的經(jīng)典三欄布局,main函數(shù)不需要修改,只需要修改MyApp結(jié)構(gòu)體的定義即可。
定義導(dǎo)航變量
先定義一個(gè)導(dǎo)航枚舉,用來(lái)在標(biāo)記當(dāng)前要顯示的界面:
//導(dǎo)航枚舉
enum Page {
Test,
Settings,
}
為了方便理解示例,在 MyApp 中只定義一個(gè) page 字段,并同步修改new函數(shù):
//定義 MyApp 結(jié)構(gòu)體
struct MyApp {
page:Page,
}
//MyApp 結(jié)構(gòu)體 new 函數(shù)
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setup_custom_fonts(&cc.egui_ctx);
// 結(jié)構(gòu)體賦初值
Self {
page:Page::Test,
}
}
}
實(shí)現(xiàn)導(dǎo)航界面
在 MyApp 中定義導(dǎo)航欄的界面,
impl MyApp {
//左側(cè)導(dǎo)航按鈕,egui沒(méi)有內(nèi)置樹(shù)控件,有需要可以自己實(shí)現(xiàn)
fn left_ui(&mut self, ui: &mut egui::Ui) {
//一個(gè)垂直布局的ui,內(nèi)部控件水平居中并對(duì)齊(填充全寬)
ui.vertical_centered_justified(|ui| {
if ui.button("測(cè)試").clicked() {
self.page=Page::Test;
}
if ui.button("設(shè)置").clicked() {
self.page=Page::Settings;
}
//根據(jù)需要定義其它按鈕
});
}
//...其它方法
}
實(shí)現(xiàn)導(dǎo)航邏輯
在 MyApp 中定義一個(gè) show_page 方法來(lái)進(jìn)行界面調(diào)度,每個(gè)界面再單獨(dú)實(shí)現(xiàn)自己的UI函數(shù)
impl MyApp {
//...其它方法
//根據(jù)導(dǎo)航顯示頁(yè)面
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"));
}
//...其它方法
}
實(shí)現(xiàn)主框架布局
在 MyApp 中間實(shí)現(xiàn) main_ui 方法,可以根據(jù)自己的需要調(diào)整各個(gè)欄的位置:
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("標(biāo)題欄");
});
ui.label("標(biāo)題欄內(nèi)容");
});
});
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("左導(dǎo)航欄");
});
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("右導(dǎo)航欄");
});
egui::ScrollArea::vertical().show(ui, |ui| {
ui.label("右導(dǎo)航欄內(nèi)容");
});
});
egui::TopBottomPanel::bottom("bottom_panel")
.resizable(false)
.min_height(0.0)
.show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
ui.heading("狀態(tài)欄");
});
ui.vertical_centered(|ui| {
ui.label("狀態(tài)欄內(nèi)容");
});
});
egui::CentralPanel::default().show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
ui.heading("主面板");
});
egui::ScrollArea::vertical().show(ui, |ui| {
ui.label("主面板內(nèi)容");
self.show_page(ui);
});
});
}
}
調(diào)試運(yùn)行
在 main 函數(shù)中稍微調(diào)整一下窗口大?。?/p>
// 創(chuàng)建視口選項(xiàng)
let mut options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([1000.0, 500.0]),
..Default::default()
};
在 update 函數(shù)中調(diào)用 main_ui 函數(shù):
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
//設(shè)置主題
ctx.set_visuals(egui::Visuals::dark());
// 在中央面板上顯示egui界面
egui::CentralPanel::default().show(ctx, |ui| {
self.main_ui(ui);
});
}
}
運(yùn)行結(jié)果如下:

以上就是Rust中GUI庫(kù)egui的簡(jiǎn)單應(yīng)用指南的詳細(xì)內(nèi)容,更多關(guān)于Rust egui的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rust并發(fā)編程之使用消息傳遞進(jìn)行線(xiàn)程間數(shù)據(jù)共享方式
文章介紹了Rust中的通道(channel)概念,包括通道的基本概念、創(chuàng)建并使用通道、通道與所有權(quán)、發(fā)送多個(gè)消息以及多發(fā)送端,通道提供了一種線(xiàn)程間安全的通信機(jī)制,通過(guò)所有權(quán)規(guī)則確保數(shù)據(jù)安全,并且支持多生產(chǎn)者單消費(fèi)者架構(gòu)2025-02-02
2022最新Rust變量與數(shù)據(jù)類(lèi)型講解
rust 是強(qiáng)類(lèi)型語(yǔ)言所有變量、常量都必須有明確的數(shù)據(jù)類(lèi)型,這篇文章主要介紹了Rust變量與數(shù)據(jù)類(lèi)型,需要的朋友可以參考下2022-11-11
關(guān)于Rust?使用?dotenv?來(lái)設(shè)置環(huán)境變量的問(wèn)題
在項(xiàng)目中,我們通常需要設(shè)置一些環(huán)境變量,用來(lái)保存一些憑證或其它數(shù)據(jù),這時(shí)我們可以使用dotenv這個(gè)crate,接下來(lái)通過(guò)本文給大家介紹Rust?使用dotenv來(lái)設(shè)置環(huán)境變量的問(wèn)題,感興趣的朋友一起看看吧2022-01-01
Rust聲明宏在不同K線(xiàn)bar類(lèi)型中的應(yīng)用小結(jié)
在K線(xiàn)bar中,往往有很多不同分時(shí)k線(xiàn)圖,比如1,2,3,5,,,,,60,120,250,300…,,不同分鐘類(lèi)型,如果不用宏,那么手寫(xiě)會(huì)比較麻煩,下面就試用一下宏來(lái)實(shí)現(xiàn)不同類(lèi)型的bar,感興趣的朋友一起看看吧2024-05-05
rust的nutyp驗(yàn)證和validator驗(yàn)證數(shù)據(jù)的方法示例詳解
本文介紹了在Rust語(yǔ)言中,如何使用nuType和validator兩種工具來(lái)對(duì)Cargo.toml和modules.rs文件進(jìn)行驗(yàn)證,通過(guò)具體的代碼示例和操作步驟,詳細(xì)解釋了驗(yàn)證過(guò)程和相關(guān)配置,幫助讀者更好地理解和掌握使用這兩種驗(yàn)證工具的方法,更多Rust相關(guān)技術(shù)資訊,可繼續(xù)關(guān)注腳本之家2024-09-09

