Skip to content

Feat/layer anims#3481

Open
Atan-D-RP4 wants to merge 14 commits intoniri-wm:mainfrom
Atan-D-RP4:feat/layer-anims
Open

Feat/layer anims#3481
Atan-D-RP4 wants to merge 14 commits intoniri-wm:mainfrom
Atan-D-RP4:feat/layer-anims

Conversation

@Atan-D-RP4
Copy link

@Atan-D-RP4 Atan-D-RP4 commented Feb 20, 2026

Implements layer animations.
There is surprisingly no open issue for this. Just a couple discussions.

260220_23h27m11s_recording.mp4

@Atan-D-RP4 Atan-D-RP4 force-pushed the feat/layer-anims branch 6 times, most recently from 13420bf to c42db0f Compare February 21, 2026 20:43
Copy link
Member

@YaLTeR YaLTeR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked through in detail

Comment on lines +152 to +154
if let Some((_, mut mapped)) = self.niri.closing_layers.remove(layer) {
mapped.clear_close_animation();
mapped.start_open_animation(&config.animations);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make more sense for it to work like closing windows work where they are not tied in any way to a Mapped. It would remove this sort of juggling.

use crate::render_helpers::{render_to_encompassing_texture, RenderTarget};

#[derive(Debug)]
pub struct ClosingLayer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much exactly the same as ClosingWindow, maybe they could be somehow combined together?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this and opening_layer.rs, I directly copied from window stuff. Combining them together would be more architectural change, and I didn't think it appropriate for this PR.

pub closing_layer: Option<ClosingLayer>,

/// Cached render elements for close animation (captured on last commit before close).
cached_close_elements: Option<CachedCloseElements>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called "unmap snapshot" for windows, let's keep the terminology

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

let buf_pos = location;

let surface = self.surface.wl_surface();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated changes

Comment on lines +272 to +291
push_elements_from_surface_tree(
renderer,
surface,
Point::from((0., 0.)).to_physical_precise_round(scale),
scale,
alpha,
Kind::ScanoutCandidate,
&mut |elem| contents.push(elem),
);

let mut blocked_out_contents = Vec::new();
push_elements_from_surface_tree(
renderer,
surface,
Point::from((0., 0.)).to_physical_precise_round(scale),
scale,
alpha,
Kind::ScanoutCandidate,
&mut |elem| blocked_out_contents.push(elem),
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff duplicated in 2 places likely needs to go into a separate function called store_unmap_snapshot() (see how window Mapped does it)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already done in a local commit.

Comment on lines +413 to +416
// Blocked out layers fall back to normal rendering
if target.should_block_out(self.rules.block_out_from) {
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Blocked-out tiles get rendered with anims, layers should too

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got the wrong idea from render_popups(). I also don't use screensharing+block-out-from much so I didn't notice.

Kind::ScanoutCandidate,
&mut |elem| contents.push(elem),
);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the logic here is to store a snapshot of contents at first commit to use if the surface is destroyed without unmapping? That's kinda weird tbh, those contents are way out of date by then. And anyhow this shouldn't be a problem I think if you store unmap snapshot in CompositorHandler::destroyed() just like regular windows do

Comment on lines +255 to +256
// The surface is unmapped. Capture elements now (while surface still has content)
// and add to closing_layers for animation rendering.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this needs to be in pre-commit hook I believe (just like normal windows unmap snapshot)

@Atan-D-RP4
Copy link
Author

Alright everything seems to work.

260224_11h17m05s_recording.mp4

@HigherOrderLogic
Copy link
Contributor

Looks like the SwayNC panel has a faint overlay during close animation.

@Atan-D-RP4
Copy link
Author

That's just how SwayNC does it. I tested it on Hyprland, and it's there as well.

@Atan-D-RP4
Copy link
Author

Atan-D-RP4 commented Feb 24, 2026

Test config for anyone looking to review this and new config design:

animations {
    layer-open {
        duration-ms 700
        curve "cubic-bezier" 0.05 0.7 0.1 1 // CSS cubic-bezier curve

        // fade in, no movement
        custom-shader r"
            vec4 open_color(vec3 coords_geo, vec3 size_geo) {
                vec3 coords_tex = niri_geo_to_tex * coords_geo;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                color *= niri_clamped_progress;
                return color;
            }
        "
    }

    layer-close {
        // base fallback close (wallpaper, and any bucket without overrides)
        duration-ms 900
        curve "cubic-bezier" 0.05 0.7 0.1 1

        // fade out, no movement
        custom-shader r"
            vec4 close_color(vec3 coords_geo, vec3 size_geo) {
                // Geometry-space y coordinate of output bottom edge.
                float bottom_geo = (niri_input_to_geo * vec3(0.0, 1.0, 1.0)).y;
                vec3 coords = coords_geo;
                coords.y -= niri_clamped_progress * bottom_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                return color;
            }
        "
    }


    layer-launcher-open {
        // distinct long launcher open
        duration-ms 2400
        curve "cubic-bezier" 0.05 0.7 0.1 1

        // slide in from left with fade
        custom-shader r"
            vec4 open_color(vec3 coords_geo, vec3 size_geo) {
                // Geometry-space x coordinate of output left edge.
                float left_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x += (1.0 - niri_clamped_progress) * left_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                color *= niri_clamped_progress;
                return color;
            }
        "
    }

    layer-launcher-close {
        // distinct long launcher close
        duration-ms 2600
        curve "cubic-bezier" 0.05 0.7 0.1 1
        // slide out to right with fade
        custom-shader r"
            vec4 close_color(vec3 coords_geo, vec3 size_geo) {
                // Geometry-space x coordinate of output right edge.
                float right_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x -= niri_clamped_progress * right_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                color *= (1.0 - niri_clamped_progress);
                return color;
            }
        "
    }

    layer-bar-open {
        // bar open distinct but shorter than launcher
        duration-ms 1800
        curve "cubic-bezier" 0.05 0.7 0.1 1
        // slide in from left
        custom-shader r"
            vec4 open_color(vec3 coords_geo, vec3 size_geo) {
                // Geometry-space x coordinate of output right edge.
                float right_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x -= (1.0 - niri_clamped_progress) * right_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                return color;
            }
        "
    }

    layer-bar-close {
        duration-ms 3200
        curve "cubic-bezier" 0.05 0.7 0.1 1

        // slide out to right
        custom-shader r"
            vec4 close_color(vec3 coords_geo, vec3 size_geo) {
                float right_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x += niri_clamped_progress * right_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                return color;
            }
        "
    }

    layer-wallpaper-open {
        duration-ms 2000
        curve "cubic-bezier" 0.05 0.7 0.1 1

        // slide in from left
        custom-shader r"
            vec4 open_color(vec3 coords_geo, vec3 size_geo) {
                // Geometry-space x coordinate of output right edge.
                float right_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x -= (1.0 - niri_clamped_progress) * right_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                return color;
            }
        "
    }

    layer-wallpaper-close {
        duration-ms 2000
        curve "cubic-bezier" 0.05 0.7 0.1 1

        // slide out to right
        custom-shader r"
            vec4 close_color(vec3 coords_geo, vec3 size_geo) {
                float right_geo = (niri_input_to_geo * vec3(1.0, 0.0, 1.0)).x;
                vec3 coords = coords_geo;
                coords.x += niri_clamped_progress * right_geo;
                vec3 coords_tex = niri_geo_to_tex * coords;
                vec4 color = texture2D(niri_tex, coords_tex.st);
                if (coords_tex.x < 0.0 || coords_tex.x > 1.0 ||
                    coords_tex.y < 0.0 || coords_tex.y > 1.0) {
                    color = vec4(0.0);
                }
                return color;
            }
        "
    }
}

@HigherOrderLogic
Copy link
Contributor

I'm not sure if I like the layer-(bar|launcher|wallpaper|whatever-(open|close) config design. Is there any rationale behind this?

I would prefer if it just use the usual layer-rules block and matchers.

@Atan-D-RP4
Copy link
Author

I'm not sure if I like the layer-(bar|launcher|wallpaper|whatever-(open|close) config design. Is there any rationale behind this?

Neither do I.

But Yalter mentioned wanting to see if such a design, with it's backing heuristic, was possible to do relatively painlessly over on matrix.

It is not as painless as desired. We'll see which parts we can keep and which to throw out.

I would prefer if it just use the usual layer-rules block and matchers.

That'll have to wait for animations block support in window-rules.

@YaLTeR
Copy link
Member

YaLTeR commented Feb 27, 2026

I'm not sure if I like the layer-(bar|launcher|wallpaper|whatever-(open|close) config design. Is there any rationale behind this?

  1. Feat/layer anims #3481 (comment)
  2. Our goal here is to have the sensible behavior by default (i.e. bars use bar anim, launchers use launcher anim), then make it easy to override common cases (i.e. someone wants to leave bar anims but disable launcher anims), then make it possible to override per client (i.e. current layer rules with namespace -- this should be the last resort as it's the least general way). All of this filtered through what is technically feasible without hacks (i.e. if it turns out that we can't make robust heuristics then we don't do them).

@Atan-D-RP4
Copy link
Author

  1. Feat/layer anims #3481 (comment)

I already replied to that thread but it was me doing something AI suggested, to make the open anim work.
Turns out I'd just been dropping the offscreen data instead of rendering it into a texture and using that.
Already cleaned up.

@YaLTeR
Copy link
Member

YaLTeR commented Feb 27, 2026

I was just linking the rationale

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants