slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
17.58k stars 604 forks source link

struct with internal array of struct incorrectly converts to ModelRc #2787

Closed flukejones closed 1 year ago

flukejones commented 1 year ago

I have two exported structs such:

export struct ParameterChoice {
    label: string,
    icon: image,
}

export struct Parameter {
    icon: image,
    choices: [ParameterChoice],
}

Parameter is used in a for loop, with choices being a nested for. The property is bound all the way to root for rust access.

And I try to work with them as:

ui_handle
            .upgrade_in_event_loop(move |handle| {
                let test: Vec<Parameter> = vec![Parameter {
                    choices: ModelRc::new(ParameterChoice {
                        icon: Image::load_from_path(&PathBuf::from(
                            "gui/ui/images/parameters/gas.png",
                        ))
                        .unwrap(),
                        index: 1,
                        label: "Some label".into(),
                    }),
                    icon: Image::load_from_path(&PathBuf::from("gui/ui/images/parameters/gas.png"))
                        .unwrap(),
                }];
                let dat = ModelRc::new(VecModel::from_slice(&test));
                handle.set_parameters(dat);
            })
            .ok();

I noticed that trying to instantiate choices as an array was an error, and only choices: ModelRc::new(ParameterChoice { would work. This looks to be due to the generated code being incorrect:

     # [derive (Default , PartialEq , Debug , Clone)] pub struct r#ParameterChoice {
         pub r#icon : slint :: private_unstable_api :: re_exports :: Image , pub r#index : i32 , pub r#label : slint :: private_unstable_api :: re_exports :: SharedString }
     # [derive (Default , PartialEq , Debug , Clone)] pub struct r#Parameter {
         pub r#choices : slint :: private_unstable_api :: re_exports :: ModelRc < r#ParameterChoice > , pub r#icon : slint :: private_unstable_api :: re_exports :: Image }

where choices is a ModelRc for some reason. Use of the struct including the incorrect field works fine in a for loop in .slint still, just not correct.

flukejones commented 1 year ago

The same is also true of a callback with array arg. Seems to not be possible to pass an array in from rust:

    callback set-toolbar([Parameter]);
    set-toolbar(parameters) => {
        page-welding.parameters = parameters;
    }

results in:

# [allow (dead_code)] pub fn set_parameters (& self , value : slint :: private_unstable_api :: re_exports :: ModelRc < r#Parameter >) {
flukejones commented 1 year ago

And also not possible to pass nested arrays.

flukejones commented 1 year ago

Uh... okay, so maybe the model documentation needs more work. I still don't fully understand it but on a whim I tried:

                    choices: VecModel::from_slice(&vec![ParameterChoice {
                        icon: Image::load_from_path(&PathBuf::from(
                            "gui/slint/ui/images/parameters/gas.png",
                        )).unwrap(),
                        index: 42,
                        label: "Test 1".to_string().into(),
                    },
                    ParameterChoice {
                        icon: Image::load_from_path(&PathBuf::from(
                            "gui/slint/ui/images/parameters/gas.png",
                        )).unwrap(),
                        index: 42,
                        label: "Test 2".to_string().into(),
                    }]),

VecModel transforms to ModelRc?

tronical commented 1 year ago

You've noticed correctly, arrays in .slint files map to models in Rust. I'll try to improve the docs on this. There's evidently something missing to make this obvious, especially that this also applies to fields of structs.

Regarding VecModel and ModelRc, there are three ways of instantiating VecModel:

  1. Via Default trait
  2. Via From trait for Vec<T>
  3. From a &[T] slice via from_slice.

The first two options create a VecModel<T> and that still needs an explicit step to turn it into a Rc<dyn Model>( aka ModelRc) via ModelRc::new(m: impl Model<..>). But you have the VecModel<T> around and can use its API to make further changes. The third option, creating a VecModel from a slice, assumes that you may want to use this right away and not do any further mutations, so the concrete model type is erased right away and VecModel<T>::from_slice already returns Rc<dyn Model> (ModelRc).

Finally, for a given ModelRc, you can always try to go back to the concrete model via as_any() and then downcast_ref().

tronical commented 1 year ago

I created https://github.com/slint-ui/slint/pull/2791 . I'm still wondering about whether we should offer From<&[T]> for ModelRc. On the one hand it's tempting, on the other hand it disguises the underlying VecModel, so it's harder to understand that if you want to make changes later that's the type you need to downcast_ref to.

flukejones commented 1 year ago

@tronical I would say yes to including a From conversion. As far as i can see in my own work the only time I will be touching ModelRc is when I need to put data in the UI - and I will be using [<T>] or Vec up until that point.