【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的内容。其官方注解如下:

    shell 复制代码
    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(或者其他)的时候被调用,一秒可能会调用多次
    (稍微看了下源代码,可能是事件驱动调用?)

  • 我们可以在该函数里加个日志看看调用情况:

    shell 复制代码
    [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的内容,首先是:

    rust 复制代码
    #[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(),其实现如下:

    rust 复制代码
    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也改成一样的:

    rust 复制代码
    egui::SidePanel::left("top_panel")

    运行后出现报错 (错误提示还挺全)

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

  • 当然我们也可以调用bottom()方法,对应枚举TopBottomSide::Bottom

  • new方法的实现如下:

    rust 复制代码
    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()方法:

    rust 复制代码
    pub fn show<R>(
        self,
        ctx: &Context,
        add_contents: impl FnOnce(&mut Ui) -> R
    ) -> InnerResponse<R>

    参数add_contentsFnOnce闭包,仅执行一次,在我们的应用中,闭包中添加了一个简单的菜单栏:

    rust 复制代码
    // 添加菜单栏
    egui::menu::bar(ui, |ui| {
        ui.menu_button("File", |ui| {
        	if ui.button("Quit").clicked() {
            	_frame.close();
            }
        });
    });
  • 注意:上面说的执行一次,是说此次update调用中执行一次

  • 我们继续深入下TopBottomPanel的定义:

    rust 复制代码
    pub struct TopBottomPanel {
        side: TopBottomSide,
        id: Id,
        frame: Option<Frame>,
        resizable: bool,
        show_separator_line: bool,
        default_height: Option<f32>,
        height_range: RangeInclusive<f32>,
    }

    其实是可以修改一些样式的,比如高度:

    rust 复制代码
    egui::TopBottomPanel::top("top_panel").min_height(100.0).show(...

menu::bar

  • TopBottomPanel中,我们使用bar()函数添加了一个菜单栏,其函数定义如下:

    rust 复制代码
    pub fn bar<R>(
        ui: &mut Ui,
        add_contents: impl FnOnce(&mut Ui) -> R
    ) -> InnerResponse<R>

    同样使用FnOnce闭包来添加一些额外的元素

  • 菜单栏组件在TopBottomPanel::top中的展示效果最好,当然也可以放在Window中。

    shell 复制代码
    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()的回调中,我们添加了一个下拉按钮

    rust 复制代码
    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闭包添加了一个按钮:

    rust 复制代码
    ui.menu_button("File", |ui| {
    	if ui.button("Quit").clicked() {
            _frame.close();
        }
    });
  • 其实我们还可以在menu_button中添加一个子menu_button

    rust 复制代码
    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

  • 上面我们已经接触到了文本按钮,其定义如下:

    rust 复制代码
    pub fn button(&mut self, text: impl Into<WidgetText>) -> Response
  • 实际上是一个简单的封装函数:

    rust 复制代码
    Button::new(text).ui(self)
  • 通常的用法是:

    rust 复制代码
    if ui.button("Click me").clicked() { ... }
  • 现在我们进一步看看Button的定义:

    rust 复制代码
    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>,
    }
  • 是有一些参数可以设置的,那我们怎样添加一个不一样的按钮呢?

    rust 复制代码
    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元素是否被点击、拖拽,进而做出对应的处理;例如点击事件:

    rust 复制代码
    pub fn clicked(&self) -> bool {
        self.clicked[PointerButton::Primary as usize]
    }

    其大致流程是:鼠标点击事件被eframe捕获,由egui计算与整个ui的交互结果,例如哪些元素被点击到了,点击结果存储到Response.clicked数组中,我们只需访问即可。
    clicked存储了五种点击事件

    rust 复制代码
    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后面还有代码的话,也会继续执行:

    rust 复制代码
    let ret = eframe::run_native(
        "demo app",
        native_options,
        Box::new(|cc| Box::new(demo_app::TemplateApp::new(cc))),
    );
    
    log::error!("end");
    
    ret

参考

相关推荐
k09339 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯17 分钟前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue19 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
plmm烟酒僧21 分钟前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv
测试界的酸菜鱼32 分钟前
Python 大数据展示屏实例
大数据·开发语言·python
晨曦_子画42 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
Black_Friend1 小时前
关于在VS中使用Qt不同版本报错的问题
开发语言·qt
希言JY1 小时前
C字符串 | 字符串处理函数 | 使用 | 原理 | 实现
c语言·开发语言
残月只会敲键盘1 小时前
php代码审计--常见函数整理
开发语言·php
xianwu5431 小时前
反向代理模块
linux·开发语言·网络·git