Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting model by boolean field causes unexpected behavior #7895

Open
0-Patient opened this issue Mar 20, 2025 · 5 comments
Open

Sorting model by boolean field causes unexpected behavior #7895

0-Patient opened this issue Mar 20, 2025 · 5 comments
Labels
a:models&views The implementation of the `for` and ListView (mO,bF) bug Something isn't working

Comments

@0-Patient
Copy link

0-Patient commented Mar 20, 2025

Bug Description

I have a structure DiscrChannel defined as follows:

export struct DiscrChannel {
    id: int,
    name: string,
    is-selected: bool,
    is-inversed: bool,
}

A ListView in the UI displays a Vec<DiscrChannel>. When clicking on any row, the is-selected property of the corresponding DiscrChannel element toggles.

However, when I sort the model by the is-inversed field, toggling the is-selected property causes the model to behave as if it is being filtered by both fields (is-inversed and is-selected). This behavior does not occur when sorting by non-boolean fields (e.g., id), which works correctly and does not break the list.

Reproducible Code (if applicable)

use std::error::Error;
use std::rc::Rc;

use slint::SortModel;

slint::slint! {
    import { ListView, Button } from "std-widgets.slint";

    export struct DiscrChannel {
        id: int,
        name: string,
        is-selected: bool,
        is-inversed: bool,
    }

    export component App inherits Window {
        title: "test";
        in property <[DiscrChannel]> discr-list;

        callback sort-id();
        callback sort-inversed();

        VerticalLayout {
            HorizontalLayout {
                Button {text: "Sort id"; clicked => {sort-id();}}
                Button {text: "Sort inversed"; clicked => {sort-inversed();}}
            }
            
            ListView {
                width: 500px;
                height: 500px;

                for channel in discr-list: TouchArea {
                    clicked => {
                        channel.is-selected = !channel.is-selected;
                    }
                    bg := Rectangle {
                        background: channel.is-selected ? #80d0d6 : #e0e0e0;
                        
                        HorizontalLayout {
                            padding: 10px;

                            Text {text: channel.id;}
                            Text {text: channel.name;}
                            Text {text: channel.is-inversed ? "+" : "-";}
                        }
                    }
                }
            }
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let ui = App::new()?;
    let ui_weak = ui.as_weak();

    let model_discr = Rc::new(slint::VecModel::from(vec![
        DiscrChannel { id: 2, name: "item 2".into(), is_selected: false, is_inversed: true },
        DiscrChannel { id: 1, name: "item 1".into(), is_selected: false, is_inversed: false },
        DiscrChannel { id: 4, name: "item 4".into(), is_selected: false, is_inversed: true },
        DiscrChannel { id: 3, name: "item 3".into(), is_selected: false, is_inversed: false },
        DiscrChannel { id: 5, name: "item 5".into(), is_selected: false, is_inversed: true },
    ]));

    ui.set_discr_list(model_discr.clone().into());

    {
        let ui_weak = ui_weak.clone();
        let model_discr = model_discr.clone();
        
        ui.on_sort_id(move || {
            let ui = ui_weak.unwrap();
            let model_discr = model_discr.clone();

            ui.set_discr_list(
                Rc::new(SortModel::new(model_discr, |a, b| a.id.cmp(&b.id))).into()
            );
        });
    }

    {
        let ui_weak = ui_weak.clone();
        let model_discr = model_discr.clone();
        
        ui.on_sort_inversed(move || {
            let ui = ui_weak.unwrap();
            let model_discr = model_discr.clone();

            ui.set_discr_list(
                Rc::new(SortModel::new(model_discr, |a, b| a.is_inversed.cmp(&b.is_inversed))).into()
            );
        });
    }

    ui.run()?;

    Ok(())
}

Environment Details

  • Slint Version: 1.10
  • Platform/OS: Windows 10
  • Programming Language: Rust 1.85
  • Backend/Renderer: winit-femtovg

Product Impact

No response

@0-Patient 0-Patient added bug Something isn't working need triaging Issue that the owner of the area still need to triage labels Mar 20, 2025
@0-Patient
Copy link
Author

After further observation, I realized that the issue is not related to boolean fields specifically. During sorting by any field, elements with the same value are additionally sorted by the is-selected property every time its value changes

@tronical
Copy link
Member

I can reproduce this. I think the row_changed changed handler of the sort model isn't preserving the order, i.e. it isn't stable.

@tronical tronical added a:models&views The implementation of the `for` and ListView (mO,bF) and removed need triaging Issue that the owner of the area still need to triage labels Mar 20, 2025
@tronical
Copy link
Member

I tried to fix this, but I couldn't quite figure out a good way. Last attempt was an almost brute-force check with is_sorted_by() on the mapping, in an attempt to short-cut row changes that shouldn't affect the sort order, but ALAS no luck.

Implementation wise, I think this affects Rust, C++, as well as future JavaScript and Python models.

@ogoffart
Copy link
Member

Is it not enough to check, in row_whanged the next and the previous element to check if the element is already at the right place?

@tronical
Copy link
Member

That's what I thought, too, but somehow that didn't work with the example:(. Maybe I need to give it another shot.

(I wish I knew a trick to add Debug easily to the type constraints within model)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:models&views The implementation of the `for` and ListView (mO,bF) bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants