【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button

说在前面

  • 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特征,并且需要全局唯一,那我要是不唯一怎么办,比如把下面的SidePanel也改成一样的:

    egui::SidePanel::left("top_panel")
    

    运行后出现报错 (错误提示还挺全) 【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第1张图片

  • 在函数实现中,实际上还是调用的new方法,传入位置的枚举TopBottomSide::Top

  • 当然我们也可以调用bottom()方法,对应枚举TopBottomSide::Bottom
    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第2张图片

  • 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_contentsFnOnce闭包,仅执行一次,在我们的应用中,闭包中添加了一个简单的菜单栏:

    // 添加菜单栏
    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(...
    

    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第3张图片

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();
            }
        });
    });
    
    效果如图
    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第4张图片
  • 如果menu_button直接放在panel中会怎样呢?
    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第5张图片
    其实也是可以的,只是效果不是很好,对比一下(上图是放在panel中,下图是放在bar中的效果):
    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第6张图片

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();
    }
    
    【rust/egui】(四)看看template的app.rs:update以及组件TopBottomPanel&Button_第7张图片
    在这里插入图片描述
  • 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
    

参考

  • Button
  • menu_button
  • bar
  • TopBottomPanel
  • update

你可能感兴趣的:(Rust,rust,命令模式,开发语言)