本文带你从零开始使用 Rust 和 egui 构建一个跨平台的图形用户界面(GUI)应用程序。我们将通过创建一个简易的"温度转换器"桌面应用,深入讲解 egui 的基本组件、事件处理机制、布局系统以及如何将逻辑与界面分离。适合已掌握 Rust 基础语法并希望拓展到 GUI 开发领域的开发者。
一、Rust 能做图形界面吗?------打破误解
长久以来,Rust 被广泛认为是一种系统级编程语言,主要用于后端服务、嵌入式开发和性能敏感型任务。然而,随着生态系统的不断成熟,Rust 在 GUI 领域也取得了显著进展。虽然它不像 Python 的 Tkinter 或 C# 的 WPF 那样拥有原生成熟的 GUI 框架,但社区已经涌现出多个高质量的 GUI 库,其中 egui 因其简洁性、即时模式(immediate mode)设计和出色的跨平台支持而备受青睐。
为什么选择 egui?
- ✅ 轻量级且无依赖:核心库不依赖操作系统原生控件。
- ✅ 即时模式 GUI:每次帧都重新绘制 UI,逻辑更直观。
- ✅ 跨平台支持:可在 Windows、macOS、Linux 上运行,并可通过 WebAssembly 编译为网页应用。
- ✅ 易于集成 :可与
eframe结合快速构建原生窗口应用。 - ✅ 活跃的社区与文档:GitHub 上星标超万,更新频繁。
本案例将以 eframe + egui 组合为基础,带你完成一个完整的 GUI 小项目 ------ 摄氏度与华氏度互转工具。
二、环境准备与项目初始化
首先确保你的系统已安装 Rust 工具链(参见案例1)。然后执行以下命令创建新项目:
bash
cargo new rust_gui_converter
cd rust_gui_converter
接下来,在 Cargo.toml 文件中添加必要的依赖项:
toml
[package]
name = "rust_gui_converter"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.24" # eframe 是 egui 的应用框架封装
egui = "0.24"
保存后运行 cargo build 下载并编译依赖。首次构建可能需要几分钟时间。
三、代码演示:构建温度转换器
下面是我们完整的核心代码,位于 src/main.rs:
rust
use eframe::egui;
use std::f32;
/// 主应用程序结构体
struct TempConverter {
celsius: f32,
fahrenheit: f32,
show_result: bool,
}
impl Default for TempConverter {
fn default() -> Self {
Self {
celsius: 0.0,
fahrenheit: 32.0,
show_result: false,
}
}
}
impl eframe::App for TempConverter {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// 设置窗口标题
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
ui.heading("🌡️ 温度转换器");
});
// 中央内容区域
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.add(egui::Label::new("输入任一温度值进行转换:"));
// 摄氏度输入框
ui.horizontal(|ui| {
ui.label("摄氏度 (°C):");
ui.add(egui::DragValue::new(&mut self.celsius).speed(0.1));
});
// 华氏度输入框
ui.horizontal(|ui| {
ui.label("华氏度 (°F):");
ui.add(egui::DragValue::new(&mut self.fahrenheit).speed(0.1));
});
// 同步逻辑:任一字段变化时自动计算另一个
if !self.show_result {
self.fahrenheit = self.celsius * 9.0 / 5.0 + 32.0;
} else {
self.celsius = (self.fahrenheit - 32.0) * 5.0 / 9.0;
}
// 切换同步方向按钮
if ui.button("锁定 → 华氏度").clicked() {
self.show_result = false;
}
if ui.button("锁定 → 摄氏度").clicked() {
self.show_result = true;
}
// 显示当前转换结果
ui.separator();
ui.colored_label(
egui::Color32::LIGHT_BLUE,
format!(
"当前转换:{:.2}°C = {:.2}°F",
self.celsius, self.fahrenheit
),
);
});
});
// 底部状态栏
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label("Built with 🦀 and egui");
});
});
}
}
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(400.0, 300.0)),
resizable: true,
..Default::default()
};
eframe::run_native(
"温度转换器",
options,
Box::new(|_cc| Box::new(TempConverter::default())),
)
}
四、代码解析与关键字高亮说明
我们来逐步拆解上述代码中的关键部分,并对重要 Rust 关键字与概念进行高亮标注:
🔹 struct TempConverter
定义了一个包含两个浮点数字段(celsius, fahrenheit)和一个布尔标志位的应用状态结构体。这是整个 GUI 的数据模型。
关键字 :
struct------ 定义自定义数据类型。
🔹 impl Default
为结构体实现 Default trait,使得可以调用 .default() 方法生成默认实例。
关键字 :
impl------ 实现 trait 或方法;trait------ 接口抽象机制。
🔹 impl eframe::App
这是核心部分。通过实现 eframe::App trait,我们将 TempConverter 变成一个可运行的 GUI 应用程序。
update(&mut self, ctx, frame):每帧被调用一次,用于更新 UI。ctx: &egui::Context:UI 上下文,所有绘图操作都需要它。
关键字 :
&mut self------ 可变借用,允许修改结构体内数据。
🔹 egui::TopBottomPanel::top(...)
创建顶部面板,通常用于放置标题或菜单栏。
关键字 :
::------ 模块路径分隔符;|ui| { ... }------ 闭包(匿名函数),传递给show方法以构建 UI。
🔹 ui.horizontal(...) 与 ui.vertical_centered(...)
控制布局方式:
horizontal:水平排列组件。vertical_centered:垂直居中排列。
关键字 :
closure(闭包)是 Rust 中的一等公民,常用于回调和 UI 构建。
🔹 egui::DragValue::new(...)
提供带拖拽功能的数值输入控件,支持鼠标左右拖动调整数值。
特性 :
.speed(0.1)控制每次拖动的变化速率。
🔹 自动同步逻辑
rust
if !self.show_result {
self.fahrenheit = self.celsius * 9.0 / 5.0 + 32.0;
} else {
self.celsius = (self.fahrenheit - 32.0) * 5.0 / 9.0;
}
实现了双向绑定的核心逻辑:当用户编辑某一字段时,另一字段自动更新。
技巧 : 使用
show_result标志位避免循环触发。
🔹 按钮交互
rust
if ui.button("锁定 → 华氏度").clicked() {
self.show_result = false;
}
button(...).clicked() 返回布尔值,表示该按钮是否在当前帧被点击。
事件模型: 即时模式 GUI 的特点是"每帧检查事件",而非传统的事件监听队列。
🔹 main() 函数启动应用
rust
eframe::run_native("温度转换器", options, Box::new(|_cc| Box::new(TempConverter::default())))
启动原生窗口应用,传入窗口名称、配置选项和应用工厂函数。
内存管理 :
Box::new(...)将对象分配在堆上,满足 trait object 要求。
五、运行效果与调试建议
运行命令:
bash
cargo run
你应该会看到一个类似如下的窗口:
+------------------------------------+
| 🌡️ 温度转换器 |
|------------------------------------|
| 输入任一温度值进行转换: |
| |
| 摄氏度 (°C): [ 25.00 ] |
| 华氏度 (°F): [ 77.00 ] |
| |
| [锁定 → 华氏度] [锁定 → 摄氏度] |
| |
| 当前转换:25.00°C = 77.00°F |
| |
| Built with 🦀... |
+------------------------------------+
调试小贴士:
-
若出现图形卡顿或无法启动,请确认系统是否安装了 OpenGL 支持。
-
可尝试添加日志输出辅助调试:
rustprintln!("C: {}, F: {}", self.celsius, self.fahrenheit);
六、进阶扩展:添加单位选择与主题切换
为了展示 egui 的灵活性,我们可以进一步增强功能:
✅ 添加温度单位选择(枚举)
rust
#[derive(Clone, Copy, Debug, PartialEq)]
enum Unit {
Celsius,
Fahrenheit,
}
// 在结构体中添加字段
unit: Unit,
✅ 添加暗色/亮色主题切换
rust
ctx.set_visuals(egui::Visuals::dark()); // 暗色主题
// 或
ctx.set_visuals(egui::Visuals::light()); // 亮色主题
✅ 添加菜单栏支持(File → Exit)
rust
egui::SidePanel::left("menu_panel").show(ctx, |ui| {
if ui.button("📁 File").clicked() {
ui.menu_button("Exit", |response| {
if response.clicked() {
_frame.quit();
}
});
}
});
这些扩展体现了 Rust 强大的类型安全与 egui 灵活的组合能力。
七、数据表格:egui 常用 UI 组件速查表
| 组件名称 | 用途 | 示例代码 |
|---|---|---|
Label |
显示静态文本 | ui.label("Hello World"); |
Button |
触发动作 | if ui.button("Click me").clicked() { ... } |
TextEdit |
文本输入框 | ui.text_edit_singleline(&mut text); |
DragValue |
数值拖动输入 | ui.add(DragValue::new(&mut num)); |
Checkbox |
布尔开关 | ui.checkbox(&mut checked, "Enable"); |
ComboBox |
下拉选择 | ComboBox::from_label("Choose").selected(...) |
Slider |
滑动条调节 | ui.add(Slider::new(&mut val, 0.0..=100.0)); |
Separator |
分隔线 | ui.separator(); |
CollapsingHeader |
可折叠区域 | `ui.collapsing("Details", |
💡 提示:所有组件均返回
Response对象,可用于检测交互状态(如点击、悬停等)。
八、分阶段学习路径:从新手到熟练使用 egui
| 阶段 | 目标 | 学习内容 | 实践建议 |
|---|---|---|---|
| 阶段1:基础认知 | 理解什么是即时模式 GUI | 阅读 egui 官方介绍 | 运行官方 demo (cargo run --example hello_world) |
| 阶段2:搭建第一个 App | 成功运行 eframe 应用 |
掌握 App trait 与 Context 使用 |
修改标题、颜色、窗口大小 |
| 阶段3:掌握常用组件 | 能独立构建表单类界面 | 学习 Button, TextEdit, Slider 等 |
制作一个"BMI计算器" |
| 阶段4:布局与响应式设计 | 合理组织 UI 结构 | 使用 TopBottomPanel, SidePanel, ScrollArea |
设计多面板仪表盘 |
| 阶段5:状态管理与事件流 | 实现复杂交互逻辑 | 使用 Rc<RefCell<T>> 或消息通道 |
构建待办事项列表(增删改查) |
| 阶段6:性能优化与发布 | 打包为独立可执行文件 | 使用 cargo bundle 或 tauri 集成 |
发布 .app / .exe 文件 |
📌 推荐资源:
- egui GitHub: https://github.com/emilk/egui
- eframe book: https://emilk.github.io/egui/index.html
- crates.io 上搜索 "egui examples" 查看开源项目
九、章节总结
在本案例中,我们完成了以下目标:
✅ 掌握了使用 egui + eframe 构建原生 GUI 应用的基本流程
通过一个实用的小工具 ------ 温度转换器,理解了如何定义状态、构建 UI、处理用户输入和实现交互逻辑。
✅ 理解了"即时模式 GUI"的工作原理
不同于传统保留模式(retained mode)GUI,egui 每帧重新构建界面,简化了状态同步问题,但也要求开发者注意性能优化。
✅ 学会了常见 UI 组件的使用方法
包括标签、按钮、拖动输入框、布局容器等,并能结合条件判断实现动态行为。
✅ 体验了 Rust 在 GUI 领域的实际能力
证明了 Rust 不仅能写高性能后台服务,也能胜任桌面应用开发,尤其适合偏好类型安全和零成本抽象的工程师。
✅ 建立了进一步学习 GUI 开发的路径
无论是走向更复杂的 druid、iced,还是结合 tauri 做前后端一体化开发,这都是一个坚实的起点。
十、延伸思考:Rust GUI 的未来趋势
尽管目前 Rust 的 GUI 生态仍处于快速发展阶段,但已有多个令人振奋的方向:
- Tauri:使用 Rust 构建安全、小巧的前端桌面应用(替代 Electron)。
- Iced:受 Elm 架构启发的声明式 GUI 框架,适合构建复杂交互界面。
- Slint:专为嵌入式设备优化的 UI 引擎,支持 QML 类似语法。
- Dioxus:类 React 的 Rust 前端框架,支持 Web、Desktop、Mobile。
选择哪一种取决于你的项目需求。但对于初学者而言,egui 是最平滑的入门之选,因为它无需学习复杂的生命周期管理或 DOM 操作,只需关注"我想画什么"。
🎯 结语
Rust 正在逐步打破"只能写命令行"的刻板印象。借助 egui 这样的现代化 GUI 框架,你可以用熟悉的语法构建出美观、高效、跨平台的桌面应用。希望这个案例为你打开了一扇通往 Rust 图形世界的大门。
本文代码已在 Windows 10 + Rust 1.75 环境下测试通过。