slint icon indicating copy to clipboard operation
slint copied to clipboard

Conditional item gets re-instantiated even if the result is the same

Open bjorn opened this issue 2 years ago • 3 comments

Platform: Linux / winit / FemtoVG / X11, Language: Rust

Consider the following code:

if (Facade.portfolio.file-name != ""): MainContent {}

When I call facade.set_portfolio, the MainContent appears to be always re-instantiated (judging by the loss of its state), even if the result of the condition stays true. This is undesirable.

I'm working it around in my application by instead doing:

MainContent {
    visible: Facade.portfolio.file-name != "";
}

But this of course keeps the instance around, even when it is not needed.

bjorn avatar Nov 17 '23 14:11 bjorn

The thing is that we have to reset the whole thing if the model is dirty in https://github.com/slint-ui/slint/blob/c695869344bc297e66463db963810ce420c9c7b9/internal/core/model.rs#L871-L872

But we could actually check if the model has changed before doing that.

Now the problem is how to compare two model. Because the generated expression in rust is sp::ModelRc::new(#model as bool), so it's always a different model. So we need to find a trick to make it work. Maybe instead of using a ModelRc for bool model, we could use something else.

ogoffart avatar Nov 17 '23 14:11 ogoffart

So we need to find a trick to make it work. Maybe instead of using a ModelRc for bool model, we could use something else.

Or maybe it is possible to do a special case check for comparing bool models? Using as_any / downcast_ref, maybe?

bjorn avatar Nov 17 '23 15:11 bjorn

I just came across this issue after updating an older private project; this seems to be a recession from 1.2.2 to 1.3.0. In my case, I had a conditional button in a video-player-like application, which is almost impossible to click because the player state is updated 60 times a second:

if (!state.playing): Button {
    icon: @image-url("icons/play-fill.svg");
    enabled: state.ready && !state.fastforward;
    clicked => {
        play();
        focus.focus();
    }
}
if (state.playing): Button {
    icon: @image-url("icons/pause-fill.svg");
    enabled: !state.fastforward;
    clicked => {
        pause();
        focus.focus();
    }
}

I could workaround this issue by "pulling the ifs inside":

Button {
    icon: !state.playing ? @image-url("icons/play-fill.svg") : @image-url("icons/pause-fill.svg");
    enabled: state.ready && !state.fastforward;
    clicked => {
        if (!state.playing) {
            play();
            focus.focus();
        } else {
            pause();
            focus.focus();
        }
    }
}

tp971 avatar May 25 '24 11:05 tp971

FYI, I experienced a similar problem here: https://github.com/slint-ui/slint/discussions/7764

As you're discussing about comparing two models: I think for my use-case this wouldn't work unfortunately. As described in https://github.com/slint-ui/slint/discussions/7764, my model contains struct items, where one of the struct members (an enum) is used for the conditional components. So when other members of the struct are modified, the models will differ, but the conditional component shall still keep its state if the condition still evaluates to true after the modification.

ubruhin avatar Feb 28 '25 07:02 ubruhin