slint icon indicating copy to clipboard operation
slint copied to clipboard

Drop shadow rendering in `for` elements does not always render underneath sibling elements

Open tronical opened this issue 4 years ago • 3 comments

The following test-case renders the green drop-shadow over the sibling rectangle, when it should not:

export Test := Window {
    background: white;

    Rectangle {
        width: 50%;
        y: 50% * parent.height + 5px;
        background: black;
    }

    Rectangle {
        width: 50%;
        height: 50%;
        background: gray;
        drop-shadow-offset-x: 10px;
        drop-shadow-offset-y: 25px;
        drop-shadow-color: green;
        drop-shadow-blur: 10px;
    }
}
Screenshot 2021-04-15 at 10 39 23

The same also happens in a repeater, when applying a negative offset:

export Test := Window {
    background: white;
 
    VerticalLayout {
        spacing: 15px;
        for i in 10: Rectangle {
            background: gray;
            drop-shadow-offset-x: 10px;
            drop-shadow-offset-y: -25px;
            drop-shadow-color: green;
            drop-shadow-blur: 10px;
        }
    }
}
Screenshot 2021-04-14 at 17 11 18

tronical avatar Apr 14 '21 15:04 tronical

One way to solve that would be with z properies: https://github.com/sixtyfpsui/sixtyfps/issues/221

Edit: Actually that's not going to help much because the for only yield to one element.

ogoffart avatar May 11 '21 11:05 ogoffart

We discussed this a bit and came up with a possible plan:

  1. A shadow applied to a for-repeated element would generate a second for, which mirrors the original for and is placed before the original for in the item tree - to ensure that shadows are rendered below.
  2. The item rendering needs to be extended to support rendering an item and its sub-tree into a texture that persists, so that it can be used for the initial shadow generation & rendering and later for the actual item rendering.

The mirroring of the original for could be done like this:

The original for is a Repeater, which holds a Rc<RefCell<RepeaterInner<C>>>. Its vector of components, etc. are kept up-to-date with the original model. We could implement the Model trait for RepeaterInner (aka ShadowModel 🕶️) and use that as input for the for that's dedicated to creating the Shadow elements. The model can provide the individual fields needed or perhaps simply an ItemRef (or equivalent).

In pseudo code this is how the lowering transformation could look like:

repeater := for foo in model: SomeElement {
    ..-shadow-..: something;

    MoreStuff { }
}

// lower to:

for item_ref in ShadowModel(repeater): Shadow {
    x: item_ref.x;
    y: item_ref.y;

    render() {
        apply_shadow_to(item_ref.texture)
    }
}


repeater := for foo in model: SomeElement {
    // without the shadow

    MoreStuff { }
}

tronical avatar Jul 12 '21 09:07 tronical

Another possibility we discussed is to lower the above code to:

for [idx] in model: Shadow {
    x: repeater[idx].x;
    y: repeater[idx].y;
    ...
}
 
repeater := for foo in model: SomeElement {
    // without the shadow

    MoreStuff { }
}

Which is basically the same

ogoffart avatar Dec 07 '21 10:12 ogoffart