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

focus() in init of a conditional panics #7617

Open
szecket opened this issue Feb 12, 2025 · 4 comments
Open

focus() in init of a conditional panics #7617

szecket opened this issue Feb 12, 2025 · 4 comments
Labels
a:models&views The implementation of the `for` and ListView (mO,bF) bug Something isn't working

Comments

@szecket
Copy link
Member

szecket commented Feb 12, 2025

Bug Description

clicking on the cells of this spreadsheet should bring up different interfaces depending on the type of data.
clicking on the "favorite" items brings up a checkbox
clicking on anything else crashes

2025-02-11 21:24:13.587 slint-lsp[5391:16758825] +[IMKClient subclass]: chose IMKClient_Modern
2025-02-11 21:24:13.587 slint-lsp[5391:16758825] +[IMKInputSession subclass]: chose IMKInputSession_Modern
thread 'main' panicked at /Users/runner/work/slint/slint/internal/core/properties.rs:523:9:
Recursion detected
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
[Info - 9:24:15 PM] Connection to server got closed. Server will restart.
true
The Slint Language Server crashed. This is a bug. Please open an issue on https://github.com/slint-ui/slint/issues
[Error - 9:24:15 PM] Server process exited with signal SIGABRT.

Inconsistency: expected [Identifier], found Node(SyntaxNode { node: [email protected], source_file: "/Users/szecket/Documents/dev/slint-ui/slint/tools/lsp/ui/components/spreadsheet.slint" })

Reproducible Code (if applicable)

import { Palette, Button, LineEdit, CheckBox } from "std-widgets.slint";
import { EditorSpaceSettings, EditorSizeSettings } from "styling.slint";
export struct AddressData {
    type: string,
    street: string,
    city: string,
    state: string,
    zip: string,
    favorite: bool}
export struct CellValue {
    kind: string, // "text", "number", "boolean"
    text-value: string,
    number-value: float,
    bool-value: bool,
}

export struct CellData {
    id: string,
    value: CellValue,
    row: int,
    col: int,
    x: length,
    y: length,
    width: length,
    height: length}

export component StringEdit {
    in property <CellData> current-cell: { id: "", row: 0, col: 0, x: 0px, y: 0px, width: 0px, height: 0px };
    in property <bool> is-visible;
    callback save(string);
    callback close-editor();

    init => {
        editor.focus();
        editor.select-all();
    }

           // Add TouchArea as first child
           TouchArea {
        width: parent.width;
        height: parent.height;
        clicked => {
            editor.focus();
        }
    }

    HorizontalLayout {
        alignment: LayoutAlignment.stretch;
        spacing: EditorSpaceSettings.default-spacing * 2;
        Rectangle {
            width: self.preferred-width;
            VerticalLayout {
                spacing: EditorSpaceSettings.default-spacing;
                editor := LineEdit {
                    text: current-cell.value.text-value;
                    accepted() => {
                        save(self.text)
                    }
                    key-pressed(event) => {
                        if (event.text == Key.Escape) {
                            close-editor();
                            return accept;
                        }
                        if (event.text == Key.Return) {
                            save(editor.text);
                            return accept;
                        }
                        return reject;
                    }
                }

                HorizontalLayout {
                    alignment: space-between;
                    CheckBox {
                        text: "Translatable";
                        checked: false;
                    }

                    CheckBox {
                        text: "Bold";
                        checked: false;
                    }
                }
            }
        }

        /*
        HorizontalLayout {
            spacing: EditorSpaceSettings.default-spacing/2;
            VerticalLayout {
                alignment: end;

                Button {
                    height: 30px;
                    text: "Save";
                    clicked => {
                        save(editor.text)
                    }
                }
            }

            VerticalLayout {
                alignment: end;
                Button {
                    height: 30px;
                    text: "Cancel";
                    clicked => {
                        close-editor()
                    }
                }
            }
        }
        */
    }
}

export component EditWindow inherits Rectangle {
    in property <CellData> current-cell: { id: "", row: 0, col: 0, x: 0px, y: 0px, width: 0px, height: 0px };
    drop-shadow-blur: 10px;
    drop-shadow-color: black.transparentize(0.5);
    drop-shadow-offset-x: 0;
    drop-shadow-offset-y: 0;
    border-radius: EditorSizeSettings.radius;
    width: self.preferred-width;
    height: self.preferred-height;
    background: Palette.alternate-background;
    callback save(string);
    callback close-editor();

    
    HorizontalLayout {
        padding: EditorSpaceSettings.default-padding;
        spacing: EditorSpaceSettings.default-spacing;
        @children // Default editor placeholder
        
        if (current-cell.value.kind == "text"): StringEdit {
            current-cell: current-cell;
            save(new-value) => {
                save(new-value)
            }
            close-editor => {
                close-editor()
            }
        }
        if (current-cell.value.kind == "boolean"): CheckBox {
            text: "Yes";
            checked: current-cell.value.bool-value;
            toggled => {
                save(self.checked ? "true" : "false");
            }
        }    }
}

component Cell inherits Rectangle {
    in property <string> id;
    in property <bool> is-header: false;
    in property <bool> is-writeable: false;
    in-out property <bool> is-editing: false;
    in property <length> cell-width: 100px;
    property <bool> cell-clicked: false;
    in property <string> text: "";
    in-out property <color> bg-color: cell-clicked ? Palette.accent-background.transparentize(0.8) : transparent;
    callback edit-clicked(CellData);

    width: cell-width;
    height: 30px;
    border-width: 1px;
    border-color: Palette.border;
    background: is-header ? Palette.foreground.transparentize(0.9) : bg-color;
    HorizontalLayout {
        Rectangle {
            width: EditorSpaceSettings.default-padding / 2;
        }

        Text {
            font-weight: is-header ? 700 : 400;
            width: root.width - EditorSpaceSettings.default-padding;
            height: root.height;
            horizontal-alignment: TextHorizontalAlignment.left;
            vertical-alignment: center;
            overflow: TextOverflow.elide;
            text: root.text;
        }
    }

    if is-writeable: TouchArea {
        clicked => {
            if (root.is-writeable) {
                root.edit-clicked({
                    id: root.id,
                    value: { kind: "text", text-value: root.text, number-value: 0.0, bool-value: false },
                    row: 0,
                    col: 0
                });
            }
        }
    }
}

export component Spreadsheet inherits Rectangle {
    in-out property <[AddressData]> addresses: [
        {
            type: "home",
            street: "123 Oak Lane",
            city: "Richmond",
            state: "VA",
            zip: "23226",
            favorite: true
        },
        {
            type: "work",
            street: "456 Corporate Blvd",
            city: "Richmond",
            state: "VA",
            zip: "23219",
            favorite: false
        },
        {
            type: "history",
            street: "123 Oak Lane",
            city: "Richmond",
            state: "VA",
            zip: "23226",
            favorite: false
        },
        {
            type: "world",
            street: "456 Corporate Blvd",
            city: "Richmond",
            state: "VA",
            zip: "23219",
            favorite: false
        }
    ];
    property <CellData> current-cell: { id: "", row: 0, col: 0, x: 0px, y: 0px, width: 0px, height: 0px };
    property <bool> edit-window-visible: false;

    width: self.preferred-width;
    height: self.preferred-height;

    VerticalLayout {
        HorizontalLayout {
            for header in ["Type", "Street", "City", "State", "ZIP", "Favorite"]: Cell {
                is-header: true;
                text: header;
            }
        }

        for address[row] in addresses: HorizontalLayout {
            for cell[col] in [
                { kind: "text", text-value: address.type, number-value: 0.0, bool-value: false },
                { kind: "text", text-value: address.street, number-value: 0.0, bool-value: false },
                { kind: "text", text-value: address.city, number-value: 0.0, bool-value: false },
                { kind: "text", text-value: address.state, number-value: 0.0, bool-value: false },
                { kind: "text", text-value: address.zip, number-value: 0.0, bool-value: false },
                { kind: "boolean", text-value: "", number-value: 0.0, bool-value: address.favorite }
            ]: Cell {
                text: cell.kind == "boolean" ? (cell.bool-value ? "Yes" : "No") : cell.text-value;
                is-writeable: true;
                edit-clicked(data) => {
                    root.current-cell = {
                        id: data.id,
                        value: cell,  // Now passing the properly typed CellValue
                        row: row,
                        col: col,
                        x: self.x,
                        y: parent.y,
                        width: self.width,
                        height: self.height
                    };
                    root.edit-window-visible = true;
                }
            }
        }
    }

    if (edit-window-visible): ew := EditWindow {
        x: current-cell.x - EditorSpaceSettings.default-padding;
        y: current-cell.y - EditorSpaceSettings.default-padding / 2;
        current-cell: current-cell;
        save(new-value) => {
            // Update the specific field based on col index
            if (current-cell.col == 0) {
                addresses[current-cell.row].type = new-value;
            } else if (current-cell.col == 1) {
                addresses[current-cell.row].street = new-value;
            } else if (current-cell.col == 2) {
                addresses[current-cell.row].city = new-value;
            } else if (current-cell.col == 3) {
                addresses[current-cell.row].state = new-value;
            } else if (current-cell.col == 4) {
                addresses[current-cell.row].zip = new-value;
            }
            root.edit-window-visible = false;
        }
        close-editor => {
            root.edit-window-visible = false;
        }
    }
}

Environment Details

nightly + PR #7540: runtime_properties

Product Impact

No response

@szecket szecket added bug Something isn't working need triaging Issue that the owner of the area still need to triage labels Feb 12, 2025
@hunger
Copy link
Member

hunger commented Feb 12, 2025

I got this example to trigger the problem right away with a few lines less:

import { LineEdit } from "std-widgets.slint";

export component StringEdit {
    init => {
        editor.focus();
    }

    editor := LineEdit { }
}

export component EditWindow inherits Rectangle {
    in property <bool> current-cell;
    
    HorizontalLayout {
        if (current-cell): StringEdit { }
    }
}

export component Spreadsheet inherits VerticalLayout {
    private property <bool> current-cell;

    init() => {
        root.current-cell = true;
    }

    EditWindow {
        current-cell: current-cell;
    }
}

The editor.focus() seems to be what actually triggers the recursion somehow.

@ogoffart
Copy link
Member

Smaller reproducer:

export component Spreadsheet inherits HorizontalLayout {
    if true: FocusScope { 
        init => {
            self.focus();
        }
    }
}

I think the layout's geometry instantiate the if which then calls init, which set the focus which then need to query the sizes to check visibility, which then query the layout cache => recursion

@ogoffart ogoffart changed the title conditional using data type to instance a component crashes focus() in init of a conditional panics Feb 12, 2025
@ogoffart ogoffart added a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) 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 a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) labels Feb 12, 2025
@hunger
Copy link
Member

hunger commented Feb 13, 2025

Depressing how much smaller you got the example :-) I hope my attempt at least helped a bit.

@ogoffart
Copy link
Member

I hope my attempt at least helped a bit.

Oh yes it did, thank you for reducing the example. I didn't even try working with the original.

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