You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust+egui属性窗口中自动展示枚举变体的字段

如何在Rust+egui属性窗口中自动展示枚举变体的字段

刚好我之前做类似的属性窗口时也碰到过这个问题——手动写match处理每个枚举变体的字段实在太繁琐了,而且后续加新变体还要改代码,完全不符合“自动”的需求。其实我们可以借助Rust的派生宏(derive macro)来搞定,不用自己手动写所有分支。下面给你两种可行的方案:

方案一:用现成库快速实现(推荐)

最省心的方式是用专门为egui设计的egui_inspect库,它能自动为你的枚举生成UI渲染代码,支持各种常见类型的控件适配。

步骤1:添加依赖

在你的Cargo.toml里加上:

[dependencies]
egui = "0.22"
strum = { version = "0.25", features = ["derive", "variant_names"] }
strum_macros = "0.25"
egui_inspect = { version = "0.4", features = ["derive"] }

步骤2:给枚举添加派生宏

修改你的枚举定义,加上Inspect派生:

use egui_inspect::Inspect;
use strum::{EnumIter, VariantNames};

#[derive(Debug, Clone, PartialEq, EnumIter, VariantNames, Inspect)]
pub enum Example {
    Empty,
    Example1 { 
        var1: String, 
        var2: i32, 
    },
    Example2 { 
        var1: String, 
        var2: i32, 
        var3: bool, 
    },
}

步骤3:在UI中自动渲染字段

保持你原来的ComboBox代码不变,在下方直接调用inspect方法就能自动渲染选中变体的所有字段:

// 你原来的ComboBox代码
ComboBox::from_label("example enum")
    .selected_text(Example::VARIANTS[
        Example::iter().position(|x| x == self.example).unwrap_or(0)
    ])
    .show_ui(ui, |ui| {
        for (ex, name) in Example::iter().zip(Example::VARIANTS) {
            ui.selectable_value(&mut self.example, ex, *name);
        }
    });

// 自动渲染选中变体的字段,用group包裹更美观
ui.group(|ui| {
    self.example.inspect(ui, "");
});

这个库会根据字段类型自动选择合适的控件:字符串用单行文本框、整数用拖拽调整框、布尔值用复选框,完全不用你手动写分支。如果后续加新的变体,只要给新变体加上字段,派生宏会自动处理。

方案二:自己写派生宏(灵活定制)

如果你不想引入额外依赖,或者需要高度定制UI,可以自己写一个派生宏来生成枚举的UI代码。

步骤1:创建proc-macro crate

先新建一个proc-macro类型的子crate(比如叫egui_enum_derive),在它的Cargo.toml里加依赖:

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "derive"] }
egui = "0.22"

步骤2:实现派生宏

在src/lib.rs里写宏代码,它会为每个枚举变体生成对应的UI渲染逻辑:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Variant, Type};

#[proc_macro_derive(EguiEnumInspector)]
pub fn derive_egui_enum_inspector(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let variants = match input.data {
        Data::Enum(data) => data.variants,
        _ => panic!("EguiEnumInspector只能为枚举类型派生"),
    };

    let match_arms = variants.into_iter().map(|variant: Variant| {
        let variant_ident = variant.ident;
        match variant.fields {
            Fields::Named(fields) => {
                let field_renderers = fields.named.into_iter().map(|f| {
                    let field_ident = f.ident.unwrap();
                    let field_type = f.ty;
                    
                    // 根据字段类型生成对应的egui控件
                    match &field_type {
                        Type::Path(path) if path.path.segments.last().unwrap().ident == "String" => {
                            quote! { ui.text_edit_singleline(&mut #field_ident); }
                        }
                        Type::Path(path) if path.path.segments.last().unwrap().ident == "i32" => {
                            quote! { ui.add(egui::DragValue::new(&mut #field_ident)); }
                        }
                        Type::Path(path) if path.path.segments.last().unwrap().ident == "bool" => {
                            quote! { ui.checkbox(&mut #field_ident, stringify!(#field_ident)); }
                        }
                        // 可以继续扩展其他类型的控件
                        _ => quote! { ui.label(format!("Unsupported type: {}", stringify!(#field_type))); }
                    }
                });
                quote! {
                    #name::#variant_ident { ref mut #field_ident, .. } => {
                        #(#field_renderers)*
                    }
                }
            }
            Fields::Unit => {
                quote! { #name::#variant_ident => {} }
            }
            _ => quote! { #name::#variant_ident(..) => {} }
        }
    });

    let expanded = quote! {
        pub trait EguiEnumInspector {
            fn ui_inspect(&mut self, ui: &mut egui::Ui);
        }

        impl EguiEnumInspector for #name {
            fn ui_inspect(&mut self, ui: &mut egui::Ui) {
                match self {
                    #(#match_arms),*
                }
            }
        }
    };

    TokenStream::from(expanded)
}

步骤3:在主crate中使用

在主crate的Cargo.toml里添加对proc-macro crate的依赖:

[dependencies]
# 其他依赖...
egui_enum_derive = { path = "./egui_enum_derive" }

然后给枚举加上自定义派生:

use strum::{EnumIter, VariantNames};
use egui_enum_derive::EguiEnumInspector;

#[derive(Debug, Clone, PartialEq, EnumIter, VariantNames, EguiEnumInspector)]
pub enum Example {
    // 你的变体定义不变
}

最后在UI中调用:

// ComboBox代码不变

ui.group(|ui| {
    self.example.ui_inspect(ui);
});

这种方式可以完全定制每个类型对应的UI控件,比如给整数加范围限制、给字符串加验证逻辑等等,适合有特殊UI需求的场景。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:02:58