说在前面
- rust新手,egui没啥找到啥教程,这里自己记录下学习过程
- 环境:windows11 22H2
- rust版本:rustc 1.71.1
- egui版本:0.22.0
- eframe版本:0.22.0
- 上一篇:这里
update
update
实际上还是eframe::App
的特征,并非egui
的内容。其官方注解如下:fn update(&mut self, ctx: &Context, frame: &mut Frame) Called each time the UI needs repainting, which may be many times per second.
update
函数会在需要重绘ui(或者其他)的时候被调用,一秒可能会调用多次
(稍微看了下源代码,可能是事件驱动调用?)- 我们可以在该函数里加个日志看看调用情况:
可以看到,当我们不进行任何操作(鼠标、键盘均不输入)时,是没有任何输出的,当按住任意一个按键后,日志开始疯狂输出,这也印证了事件驱动的猜想。[2023-08-20T07:44:02Z ERROR demo_app::app] update [2023-08-20T07:44:02Z ERROR demo_app::app] update [2023-08-20T07:44:02Z ERROR demo_app::app] update [2023-08-20T07:44:02Z ERROR demo_app::app] update [2023-08-20T07:44:02Z ERROR demo_app::app] update [2023-08-20T07:44:07Z ERROR demo_app::app] update [2023-08-20T07:44:07Z ERROR demo_app::app] update
- 其他内容本文暂未深入
TopBottomPanel
-
接下来正式开始接触egui的内容,首先是:
#[cfg(not(target_arch = "wasm32"))] // 非wasm才有 egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { // 顶部的panel通常用于菜单栏 egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { if ui.button("Quit").clicked() { _frame.close(); } }); }); });
-
看看这里面是些什么,首先是
top()
,其实现如下:pub fn top(id: impl Into<Id>) -> Self { Self::new(TopBottomSide::Top, id) } // id需要是全局唯一的, e.g. Id::new("my_top_panel").
参数
id
:需要实现Into<Id>
特征,并且需要全局唯一,那我要是不唯一怎么办,比如把下面的SidePanel
也改成一样的:egui::SidePanel::left("top_panel")
运行后出现报错
(错误提示还挺全) -
在函数实现中,实际上还是调用的
new
方法,传入位置的枚举TopBottomSide::Top
-
当然我们也可以调用
bottom()
方法,对应枚举TopBottomSide::Bottom
-
new
方法的实现如下:pub fn new(side: TopBottomSide, id: impl Into<Id>) -> Self { Self { side, id: id.into(), // 调用into方法,转为Id类型 frame: None, resizable: false, // 下面是一些控制参数 show_separator_line: true, default_height: None, height_range: 20.0..=f32::INFINITY, } }
-
紧跟
top()
的是show()
方法:pub fn show<R>( self, ctx: &Context, add_contents: impl FnOnce(&mut Ui) -> R ) -> InnerResponse<R>
参数
add_contents
:FnOnce
闭包,仅执行一次,在我们的应用中,闭包中添加了一个简单的菜单栏:// 添加菜单栏 egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { if ui.button("Quit").clicked() { _frame.close(); } }); });
-
注意:上面说的执行一次,是说此次
update
调用中执行一次 -
我们继续深入下
TopBottomPanel
的定义:pub struct TopBottomPanel { side: TopBottomSide, id: Id, frame: Option<Frame>, resizable: bool, show_separator_line: bool, default_height: Option<f32>, height_range: RangeInclusive<f32>, }
其实是可以修改一些样式的,比如高度:
egui::TopBottomPanel::top("top_panel").min_height(100.0).show(...
menu::bar
- 在
TopBottomPanel
中,我们使用bar()
函数添加了一个菜单栏,其函数定义如下:
同样使用pub fn bar<R>( ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R ) -> InnerResponse<R>
FnOnce
闭包来添加一些额外的元素 - 菜单栏组件在
TopBottomPanel::top
中的展示效果最好,当然也可以放在Window
中。The menu bar goes well in a TopBottomPanel::top, but can also be placed in a Window. In the latter case you may want to wrap it in Frame.
放到bottom会盖住菜单(File):
menu::menu_button
- 在
bar()
的回调中,我们添加了一个下拉按钮
似乎pub fn menu_button<R>( ui: &mut Ui, title: impl Into<WidgetText>, add_contents: impl FnOnce(&mut Ui) -> R ) -> InnerResponse<Option<R>> Construct a top level menu in a menu bar.
menu_button
最好包在menu bar
中 - 同时也使用了
FnOnce
闭包添加了一个按钮:ui.menu_button("File", |ui| { if ui.button("Quit").clicked() { _frame.close(); } });
- 其实我们还可以在
menu_button
中添加一个子menu_button
:
效果如图ui.menu_button("File", |ui| { if ui.button("Quit").clicked() { _frame.close(); } ui.menu_button("QuitMenu", |ui| { if ui.button("Quit").clicked() { _frame.close(); } }); });
- 如果
menu_button
直接放在panel
中会怎样呢?
其实也是可以的,只是效果不是很好,对比一下(上图是放在panel
中,下图是放在bar
中的效果):
Ui::button
- 上面我们已经接触到了文本按钮,其定义如下:
pub fn button(&mut self, text: impl Into<WidgetText>) -> Response
- 实际上是一个简单的封装函数:
Button::new(text).ui(self)
- 通常的用法是:
if ui.button("Click me").clicked() { … }
- 现在我们进一步看看
Button
的定义:pub struct Button { text: WidgetText, shortcut_text: WidgetText, wrap: Option<bool>, /// None means default for interact fill: Option<Color32>, stroke: Option<Stroke>, sense: Sense, small: bool, frame: Option<bool>, min_size: Vec2, rounding: Option<Rounding>, image: Option<widgets::Image>, }
- 是有一些参数可以设置的,那我们怎样添加一个不一样的按钮呢?
if ui .add(egui::Button::new("q") // .fill(Color32::GOLD) .min_size(egui::Vec2 { x: 20.0, y: 100.0 })) .clicked() { _frame.close(); }
ui.button()
、ui.add()
返回的都是Response
,它可以让我们知道ui元素是否被点击、拖拽,进而做出对应的处理;例如点击事件:
其大致流程是:鼠标点击事件被pub fn clicked(&self) -> bool { self.clicked[PointerButton::Primary as usize] }
eframe
捕获,由egui
计算与整个ui的交互结果,例如哪些元素被点击到了,点击结果存储到Response.clicked
数组中,我们只需访问即可。
clicked存储了五种点击事件pub enum PointerButton { /// The primary mouse button is usually the left one. /// 通常是鼠标左键 Primary = 0, /// The secondary mouse button is usually the right one, /// and most often used for context menus or other optional things. /// 通常是鼠标右键 Secondary = 1, /// The tertiary mouse button is usually the middle mouse button (e.g. clicking the scroll wheel). /// 通常是鼠标中键 Middle = 2, /// The first extra mouse button on some mice. In web typically corresponds to the Browser back button. Extra1 = 3, /// The second extra mouse button on some mice. In web typically corresponds to the Browser forward button. Extra2 = 4, }
eframe::Frame::close
- 调用该函数会通知eframe关闭应用,调用后应用不会立即关闭,而是在该帧结束的时候关闭
- 同时,如果
crate::run_native
后面还有代码的话,也会继续执行:let ret = eframe::run_native( "demo app", native_options, Box::new(|cc| Box::new(demo_app::TemplateApp::new(cc))), ); log::error!("end"); ret