Element Z Indexes

Elements do NOT always render in the same order they were added. Instead, elements are simply sorted by their "Z Index", with lower z index values appearing below higher z index values.

An element's z index can be set from either the .z_index() property in the element's builder or from the .el.set_z_index() method on the element's handle.

Mouse Events

The z index also affects the order in which elements in the application receive mouse events. Elements with a higher z index value get sent a mouse event before elements with a lower z index. If an element with a higher z index "captures" the mouse event, then elements with a lower z index will not receive that mouse event.

Builder Shorthands

Setting the z index on every single element builder can be cumbersome. Luckily, a WindowContext has a concept of a "z index stack" where elements that don't have a defined z index will fall back to the most recently pushed z index on that stack. For example:

pub fn my_builder(cx: &mut WindowContext<'_, MyAction>) -> Self {
    // This element will have the default z index of "0".
    let label1 = Label::builder().build(cx);

    cx.push_z_index(5);

    // These elements will have a z index of "5".
    let label2 = Label::builder().build(cx);
    let label3 = Label::builder().build(cx);

    // This element will have a z index of "20".
    let label4 = Label::builder().z_index(20).build(cx);

    // This element will have a z index of "5".
    let label5 = Label::builder().build(cx);

    cx.push_z_index(10);

    // This element will have a z index of "10".
    let label6 = Label::builder().build(cx);

    cx.pop_z_index();

    // This element will have a z index of "5".
    let label7 = Label::builder().build(cx);

    cx.pop_z_index();

    // This element will have the default z index of "0".
    let label8 = Label::builder().build(cx);
}

Yarrow also includes a with_z_index method that automatically calls cx.push_z_index() and cx.pop_z_index() for you:

pub fn my_builder(cx: &mut WindowContext<'_, MyAction>) -> Self {
    // This element will have the default z index of "0".
    let label1 = Label::builder().build(cx);

    let (label2, label3) = cx.with_z_index(5, |cx| {
        // These elements will have a z index of "5".
        let label2 = Label::builder().build(cx);
        let label3 = Label::builder().build(cx);

        (label2, label3)
    });

    // This element will have the default z index of "0".
    let label4 = Label::builder().build(cx);
}

There is also a with_z_index_and_scissor_rect method that lets you set both a z index and a scissoring rectangle ID at once.

Performance Considerations

Currently, Yarrow treats each z index as a separate batch of primitives to send to the GPU. To keep the number of GPU draw calls and GPU memory usage down to a minimum, use as few z indexes in your application as possible.

In some cases, strategically grouping elements together into different z indexes can actually improve performance, especially with elements that have expensive text and mesh primitives. If elements that update/animate frequently are grouped into one z index and elements that rarely update are grouped into a different z index, then Yarrow/RootVG can skip preparing a batch of primitives for the rarely-updating elements whenever a frequently-updated element updates.

An optimization may be added to Yarrow/RootVG in the future to automatically batch non-overlapping primitives together to reduce GPU draw calls and GPU memory usage (and the ability to turn this feature off for certain arbitrary z indexes). I haven't gotten around to it yet, and I want to wait and see if it's even a necessary optimization.