如何在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
相关产品推荐
相关产品推荐

