1

I want to add properties window with auto properties based on struct fields. I'am downcasting every field, but for Vec<> and Option<> I have to duplicate all code, how can I automatically check is that any Option or Vec.

Downcaster:

pub fn downcast_property(ui: &mut Ui, title: &str, value_mut: &mut (dyn Any + 'static), interactable: bool) {
    if let Some(string_v) = value_mut.downcast_mut::<String>() {
        string_property(ui, title, string_v, interactable);
    } else if let Some(int_v) = value_mut.downcast_mut::<i32>() {
        i_property(ui, title, int_v, interactable);
    } else if let Some(float_v) = value_mut.downcast_mut::<f32>() {
        f_property(ui, title, float_v, interactable);
    } else if let Some(usize_v) = value_mut.downcast_mut::<usize>() {
        i_property(ui, title, usize_v, interactable);
    } else if let Some(bool_v) = value_mut.downcast_mut::<bool>() {
        bool_property(ui, title, bool_v, interactable);
    } else if downcast_all_enum_properties(value_mut, ui, title, interactable, true) {
        // Automaticall enum downcaster
    } else if let Some(named_type_value) = value_mut.downcast_mut::<NamedTypeValue>() {
        namedvalue_property(ui, title, named_type_value, interactable);
    } else if let Some(argument_named_value) = value_mut.downcast_mut::<ArgumentNamedValue>() {
        argnamedvalue_property(ui, title, argument_named_value, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<String>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<i32>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<f32>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<bool>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<DataType>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<ValueType>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<NamedTypeValue>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(v) = value_mut.downcast_mut::<Vec<ArgumentNamedValue>>() {
        list_property(ui, title, v, interactable);
    } else if let Some(o) = value_mut.downcast_mut::<Option<DataType>>() {
        option_propery(ui, title, o, interactable);
    } else if let Some(o) = value_mut.downcast_mut::<Option<String>>() {
        option_propery(ui, title, o, interactable);
    } else if let Some(o) = value_mut.downcast_mut::<Option<usize>>() {
        option_propery(ui, title, o, interactable);
    } else {
        ui.label(RichText::new(format!("Field {}: unknown type", title)).color(Color32::LIGHT_RED));
    }
}

list_property and option_property functions:

pub fn list_property<T>(ui: &mut Ui, title: &str, value_mut: &mut Vec<T>, interactable: bool)
    where T: Default + 'static {
    egui::CollapsingHeader::new(title)
        .default_open(true)
        .enabled(interactable)
        .show(ui, |ui| {
        
        ui.horizontal_top(|ui| {
            if ui.button("➕ Add").clicked() {
                value_mut.push(T::default());
            }
        });

        let mut i = 0;
        while i < value_mut.len() {
            ui.horizontal(|ui| {
                downcast_property(ui, "", &mut value_mut[i], interactable);
                if ui.button("❌").on_hover_text("Remove").clicked() {
                    value_mut.remove(i);
                } else {
                    i += 1;
                }
            });
        }
    });
}

pub fn option_propery<T>(ui: &mut Ui, title: &str, value_mut: &mut Option<T>, interactable: bool)
    where T: Default + 'static {
        
    let mut is_option_none = value_mut.is_none();
        
    ui.horizontal(|ui| {
        ui.label(title);
        ui.add_space(PROPERTY_LABEL_PADDING);
        bool_property(ui, "None", &mut is_option_none, interactable);
        ui.add_space(2.0);
        if is_option_none {
            *value_mut = None;
        } else {
            if let Some(inner) = value_mut.as_mut() {
                downcast_property(ui, "", inner, interactable);
            } else {
                *value_mut = Some(T::default());
            }
        }
    });
}
1
  • Due to how downcasting is based comparing opaque TypeId's, it's impossible to downcast Any into Option<T> or Vec<T> without statically knowing what the T is. If you must use Any, consider a macro to reduce code duplication. Commented Aug 2 at 14:49

1 Answer 1

3

I think you're approaching this from the wrong perspective. Instead of trying to figure out how to do this with a dyn Any, you could use your own trait and implement it on the types that you can provide a UI for. For example:

trait UiProperty: Default {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool);
}

The implementations for e.g. ints, floats, and strings are trivial (as are those for the other types you handle, but to keep the example brief I'm just showing a few here):

impl UiProperty for i32 {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool) {
        i_property(ui, title, self, interactable);
    }
}

impl UiProperty for f32 {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool) {
        f_property(ui, title, self, interactable);
    }
}

impl UiProperty for String {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool) {
        string_property(ui, title, self, interactable);
    }
}

For Vec<T> and Option<T> you can write a generic implementation for any T: UiProperty:

impl<T: UiProperty> UiProperty for Vec<T> {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool) {
        list_property(ui, title, self, interactable);
    }
}

impl<T: UiProperty> UiProperty for Option<T> {
    fn ui_property(&mut self, ui: &mut Ui, title: &str, interactable: bool) {
        option_property(ui, title, self, interactable);
    }
}

This would require changing your list_property and option_property functions to bound on T: UiProperty and use this trait's method internally as well, instead of recursing into downcast_property.

The downside to this approach is that it isn't easy to provide an "unknown type" error label as in your code, but this is because it will be impossible to even use any types that don't implement this trait. This way, you get a compile-time guarantee that you won't be given types that you can't handle in the UI.

Notably this also gives you the ability to handle arbitrarily-nested types like Vec<Vec<Option<String>>> for free.


Side note: Given this trait, it may make sense to simply move the bodies of the various *_property functions into the trait implementations instead of delegating to them. I used delegation for the sake of keeping the example simple, but it would be cleaner to just refactor the free functions as implementations of the trait.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.