egui Hello World 示例全解析:从Label、TextEdit、Slider到Button的即时模式 GUI 入门
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
本文以 egui 仓库中的 hello_world 示例 为主线,逐行剖析一个最小可运行的 eframe 桌面应用:从cargo run -p hello_world的启动方式,到Label、TextEdit、Slider、Button四大基础控件的用法,再到include_image!宏与图片加载器的接入原理。读完本文,你将掌握如何用 egui 在几分钟内搭起第一个带输入框、滑条、按钮和图片的 Rust 原生窗口应用,并理解其背后的核心 API 调用链。
一、示例概览与快速运行
hello_world 是 egui 仓库中最基础的示例,其 README 只有一句话的定位:展示Label、TextEdit、Slider、Button等基础 UI 控件。该示例属于仓库根目录 Cargo.toml 中examples/*工作区成员,因此可以站在仓库根目录直接用包名运行:
cargo run -p hello_world运行后会出现一个 320×240 的窗口,标题为 “My egui App”,界面包含:
- 标题文本 “My egui Application”;
- 一个 "Your name:" 标签与单行文本输入框(默认内容为
Arthur); - 一个带 "age" 文字的滑条(范围 0..=120,默认值 42);
- 一个 "Increment" 按钮,点击后年龄 +1;
- 一行根据输入实时拼接的问候语
Hello 'xxx', age xxx; - 窗口底部一张内嵌的 egui 吉祥物 ferris 图片。
整个示例的完整源码位于 examples/hello_world/src/main.rs,全部代码约 60 行,非常适合作为新手的第一个 egui 程序。
二、程序入口与原生窗口配置
2.1 隐藏 Windows 控制台窗口
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release这一行是 Windows 平台惯例:在 release 构建(cfg_attr中的not(debug_assertions))下,将子系统指定为windows,从而隐藏弹出的黑色控制台窗口。调试构建不受影响,控制台仍会保留以便观察日志。
2.2 日志初始化
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).env_logger是 egui 生态惯用的日志后端。示例中通过RUST_LOG环境变量控制输出级别,例如:
RUST_LOG=debug cargo run -p hello_world即可在 stderr 看到 eframe、egui 运行时的调试日志。该依赖在 examples/hello_world/Cargo.toml 中声明,并启用了auto-color(终端着色)与humantime(人类可读时间戳)两个特性。
2.3 ViewportBuilder 与 NativeOptions
let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() };egui::ViewportBuilder负责描述窗口/视口(viewport)的初始属性,with_inner_size设定客户区尺寸为 320×240 逻辑像素。除尺寸外,它还可以配置窗口标题、位置、是否可缩放、图标等;NativeOptions是 eframe 原生后端(基于 winit)的全局选项集合,除viewport外还包含renderer(渲染后端选择)、persist_window(是否记住窗口位置)等字段。示例只覆盖viewport,其余用..Default::default()取默认值。
三、eframe::run_native与Apptrait:应用骨架
3.1 启动函数
eframe::run_native( "My egui App", options, Box::new(|cc| { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); Ok(Box::<MyApp>::default()) }), )eframe::run_native是原生(桌面)平台的应用入口,声明于 crates/eframe/src/lib.rs,其三个参数依次为:
| 参数 | 类型 | 作用 |
|---|---|---|
app_name | &str | 应用名称,会作为窗口标题与持久化 ID 的一部分 |
native_options | NativeOptions | 上述原生窗口/渲染配置 |
app_creator | 闭包 | 创建App实例的工厂,可拿到CreationContext |
闭包接收cc: &eframe::CreationContext,其中cc.egui_ctx是全局的egui::Context,install_image_loaders正是注册到它上面。注意返回值是eframe::Result,因此main的签名是fn main() -> eframe::Result。
从仓库的调用链看,run_native会继续进入run_native_ext(crates/eframe/src/lib.rs),最终由各原生后端(如glow_integration.rs、wgpu_integration.rs)驱动事件循环与渲染。
3.2 应用状态结构体
struct MyApp { name: String, age: u32, } impl Default for MyApp { fn default() -> Self { Self { name: "Arthur".to_owned(), age: 42, } } }即时模式(immediate mode)GUI 的核心理念:UI 状态就存放在你的普通 Rust 结构体中,没有独立的控件对象树。MyApp的两个字段分别被TextEdit和Slider以&mut方式直接绑定,这正是Box::<MyApp>::default()能直接作为应用实例的原因——Default提供了初始状态。
3.3 实现eframe::App
impl eframe::App for MyApp { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ui, |ui| { // ... }); } }注意:本仓库当前版本(workspace 版本号见 Cargo.toml,为 0.36.2)的Apptrait 把绘制入口统一收口为fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame),而不是更早版本中常见的fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame),这是新版本 API 的显著变化。CentralPanel会占据窗口中央的剩余区域,是所有非面板 UI 的默认容器;ui参数则代表当前正在绘制的面板画布,示例中所有控件都挂在它下面。
四、四大基础控件逐一拆解
4.1Label:静态文本与无障碍绑定
ui.heading("My egui Application");ui.heading是Label的便捷封装,按TextStyle::Heading样式绘制大标题。随后:
let name_label = ui.label("Your name: "); ui.text_edit_singleline(&mut self.name) .labelled_by(name_label.id);第一行ui.label创建普通Label,并捕获其返回的Response中的id;第二行通过.labelled_by(...)把输入框与该标签关联。这一做法服务于无障碍(AccessKit)场景:屏幕阅读器可以把标签文本与输入框语义绑定,供依赖辅助技术的用户使用。egui 的Response是所有控件交互结果的统一载体(是否被点击、悬停、拖拽等),也是这类链式配置的入口。
4.2TextEdit:单行文本输入
ui.text_edit_singleline(&mut self.name)text_edit_singleline是TextEdit的单行便捷构造,直接借用&mut String,用户每次按键都会写回self.name,无需任何手动同步——这是即时模式最直观的体现。对应还有ui.text_edit_multiline多行版本。TextEdit位于 crates/egui/src/widgets/text_edit/ 目录,内部还实现了光标状态、IME 输入法合成等细节,这里按下不表。
4.3Slider:数值拖拽与钳制
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));Slider::new的第一个参数是被控数值的可变引用(支持整数、浮点等数值类型),第二个参数是闭区间RangeInclusive,定义滑条两端对应的取值边界。.text("age")会在滑条右侧追加说明文字。其底层实现见 crates/egui/src/widgets/slider.rs:滑条由“滑轨 + 数值显示 + 可选文本”三部分构成,数值显示部分可点击后直接键入;默认SliderClamping::Always会把值钳制在区间内,也可通过.clamping(SliderClamping::Edits)等策略调整(见 slider.rs)。示例用0..=120限定了年龄范围,恰好与u32语义吻合。
4.4Button:点击事件
if ui.button("Increment").clicked() { self.age += 1; }ui.button创建文本按钮并立即返回Response,.clicked()返回“本帧内是否发生点击”。由于即时模式每帧都会重建 UI,这里if clicked()的写法等价于传统事件循环中的回调,但更直白。按钮的完整能力不止于此——crates/egui/src/widgets/button.rs 显示Button还支持.selected()(可选中态,自动添加CLASS_SELECTED)、.fill()(自定义填充色)、.min_size()以及Button::image/Button::image_and_text(带图标按钮)等。点击后self.age += 1,下一帧滑条与问候语会自动反映新值。
4.5 实时反馈输出
ui.label(format!("Hello '{}', age {}", self.name, self.age));Label接收任意WidgetText可转换类型,format!生成的字符串在这里每帧重新求值,因此输入框内容、年龄变化都会即时反映到这一行文字上——无需任何数据绑定框架。
五、图片加载:install_image_loaders与include_image!
示例在创建应用时调用了egui_extras::install_image_loaders(&cc.egui_ctx)。该函数定义于 crates/egui_extras/src/loaders.rs,按编译特性注册一系列加载器到egui::Context:
| egui_extras 特性 | 注册的加载器 | 支持的来源 |
|---|---|---|
file(非 Wasm) | FileLoader | file://URI,经std::fs::read读取,扩展名推断类型 |
http | EhttpLoader | http(s)://URI,按Content-Type推断类型 |
image | ImageCrateLoader | png/jpeg 等,基于imagecrate |
svg | SvgLoader | .svg文件 |
示例的 Cargo.toml 中egui_extras启用了default与image两个特性,因此本示例可解码 png 等位图。函数内部会先检查ctx.is_loader_installed(...)避免重复安装,多次调用是安全的。
绘制部分:
ui.image(egui::include_image!( "../../../crates/egui/assets/ferris.png" ));include_image!是 egui 提供的编译期图片内嵌宏,定义于 crates/egui/src/lib.rs。它展开为ImageSource::Bytes:URI 固定为bytes://前缀拼接原始路径,字节数据通过include_bytes!在编译期打包进二进制,从而运行时零文件读取,特别适合图标、吉祥物等小体积资源。ui.image(...)则完成解码、上传 GPU 纹理并绘制;示例中的图片是仓库自带的 crates/egui/assets/ferris.png。
注意
install_image_loaders只负责“字节 → 图像”的解码管线,include_image!负责“文件 → 字节”的编译期内嵌,二者一前一后配合:前者提供解码能力,后者提供内嵌字节。
六、依赖配置说明
示例的 Cargo.toml 依赖结构如下:
[dependencies] eframe = { workspace = true, features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } # For image support: egui_extras = { workspace = true, features = ["default", "image"] } env_logger = { workspace = true, features = ["auto-color", "humantime"] }三个要点:
eframe是桌面端最上层的开箱即用框架(自带 winit 事件循环与渲染器),版本与 workspace 统一(当前 0.36.2)。__screenshot是一个内部特性:设置环境变量EFRAME_SCREENSHOT_TO指向输出路径后,可自动导出首帧截屏,仓库的截图回归测试与示例截图正是靠它生成的。egui_extras仅在示例中用于install_image_loaders,所以只额外开启了image特性(见 loaders.rs 中关于特性与格式的警告:加载器和图像格式必须同时配置才会生效)。- 包本身
publish = false,是仓库内部示例,不会发布到 crates.io;edition 为 2024,rust-version要求 1.95。
七、扩展方向:从示例走向真实应用
hello_world 演示的只是最小闭环,想继续深入可以沿着以下路径在仓库中找到更丰富的参照:
- 更多控件:仓库的 egui_demo_lib 中收录了数十个控件演示(滑条、拖拽值、颜色选择器、表格等),
cargo run -p egui_demo_app可运行完整的 demo 应用逐个体验; - 布局:除了
CentralPanel,egui 还提供TopBottomPanel、SidePanel等面板与ui.horizontal、ui.vertical布局函数,示例中ui.horizontal已用于把标签和输入框排成一行; - Web 端:eframe 同时支持编译到 Wasm 在浏览器运行,可参考 crates/eframe/src/web/ 下的 web 后端;
- 截图自动化:如需为 UI 生成回归快照,可参考 egui_kittest 的 snapshot 测试 以及 scripts/accept_snapshots.sh 工具链。
总而言之,hello_world 用最少的代码串起了“状态结构体 →App::ui→ 各控件读写状态”的完整循环,是理解 egui 即时模式心智模型的最佳起点:你写 UI 就是写普通 Rust 函数,状态变化由控件直接写回你的变量,渲染与事件由框架每帧自动完成。
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考