theme: workspaces drift behind the hull instead of over the bar

The hull moves to the top layer, with its transparent opening and empty input
region, so windows sliding out of the opening during a workspace switch pass
behind the frame like things behind a porthole rim rather than drawing over
the bar. A layer rule keeps the bar and the rail above it on that layer.

The animation tree is redone around that: workspaces drift 18% vertically
and fade on a slow, jolt-free "drift" curve; the SUPER+S scratchpad slides in
whole from under the bar, the same gesture as the dashboard and popouts;
windows and layers keep their calm resolve and fade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nvrlandClaude Opus 5.5 committed 2026-10-04 21:13:11 +02:00
1 parent 63a910bf14
commit 9af24ccf87
4 files changed
+55 -18

No files matched your search

+25 -8
View File
@@ -4,10 +4,22 @@
-- overshoots, nothing pops. Matches Theme.easeSnap / easeGlide in -- overshoots, nothing pops. Matches Theme.easeSnap / easeGlide in
-- ~/.config/quickshell/config/Theme.qml. -- ~/.config/quickshell/config/Theme.qml.
-- --
-- Windows fade in from a hair smaller, the way a NieR menu panel resolves -- The screen is a porthole: the hull (quickshell/bar/Frame.qml) sits on the
-- rather than arrives. Workspaces slide a short way and fade instead of -- top layer, so anything that moves past the edge of the opening passes
-- sweeping the whole screen across, which on a 2560px panel is a lot of -- behind the frame instead of over the bar.
-- motion for "you are somewhere else now". The border angle never animates. --
-- windows resolve in from a hair smaller and fade, the way a NieR menu
-- panel appears rather than arrives; leave the same way, faster.
-- workspaces drift: the old one sinks a little way behind the rim as it
-- fades, the new one rises into its place. A short, slow
-- vertical drift rather than a full-screen sweep, which on a
-- 2560px panel is a lot of motion for "you are somewhere else
-- now". Direction follows workspace order.
-- scratchpad a drawer: SUPER+S lowers it out from under the bar, the same
-- gesture as the dashboard and the popouts, and raises it back.
-- layers only fade; quickshell animates its own surfaces.
--
-- Nothing overshoots and the border angle never animates.
hl.config({ hl.config({
animations = { animations = {
@@ -23,6 +35,9 @@ hl.curve("standard", { type = "bezier", points = { { 0.40, 0.00 }, { 0.20, 1.00
-- Leaving: ease in, so the exit gathers pace and is gone. -- Leaving: ease in, so the exit gathers pace and is gone.
hl.curve("exit", { type = "bezier", points = { { 0.55, 0.00 }, { 0.90, 0.35 } } }) hl.curve("exit", { type = "bezier", points = { { 0.55, 0.00 }, { 0.90, 0.35 } } })
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } }) hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
-- easeInOutSine-ish with a long tail: starts without a jolt, settles slowly.
-- For motion big enough that a fast start would read as a shove.
hl.curve("drift", { type = "bezier", points = { { 0.35, 0.00 }, { 0.15, 1.00 } } })
-- --------------------------------------------------------------------- tree -- --------------------------------------------------------------------- tree
-- speed is in ds (1ds = 100ms). Unset leaves inherit their parent. -- speed is in ds (1ds = 100ms). Unset leaves inherit their parent.
@@ -48,8 +63,10 @@ hl.animation({ leaf = "layers", enabled = true, speed = 2.6, bezier = "glide" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 2.6, bezier = "glide", style = "fade" }) hl.animation({ leaf = "layersIn", enabled = true, speed = 2.6, bezier = "glide", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "exit", style = "fade" }) hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "exit", style = "fade" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 4, bezier = "glide", style = "slidefadevert 12%" }) hl.animation({ leaf = "workspaces", enabled = true, speed = 5.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 4, bezier = "glide", style = "slidefadevert 12%" }) hl.animation({ leaf = "workspacesIn", enabled = true, speed = 5.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 3, bezier = "exit", style = "slidefadevert 12%" }) hl.animation({ leaf = "workspacesOut", enabled = true, speed = 4.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 4, bezier = "glide", style = "slidefadevert 20%" }) hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 4.5, bezier = "glide", style = "slidevert" })
hl.animation({ leaf = "specialWorkspaceIn", enabled = true, speed = 4.5, bezier = "glide", style = "slidevert" })
hl.animation({ leaf = "specialWorkspaceOut", enabled = true, speed = 3.5, bezier = "exit", style = "slidevert" })
+10
View File
@@ -11,3 +11,13 @@ for _, namespace in ipairs({ "bottom", "top", "takemi-launcher" }) do
ignore_alpha = 0.5, ignore_alpha = 0.5,
}) })
end end
-- The hull shares the top layer with the bar and the rail, which draw on it,
-- so they are ordered above it whatever order they map in. See
-- quickshell/bar/Frame.qml.
for _, namespace in ipairs({ "takemi-bar", "takemi-rail" }) do
hl.layer_rule({
match = { namespace = namespace },
order = -1,
})
end
+12 -5
View File
@@ -310,8 +310,11 @@ network and bluetooth, but keep running as the secret and pairing agents.
borders running from the light into the suit's grey. The shadow is soft and borders running from the light into the suit's grey. The shadow is soft and
wide, so windows float in the opening. Group tabs are solid hull slabs, the wide, so windows float in the opening. Group tabs are solid hull slabs, the
current one lifted a step with its title in the light. Motion current one lifted a step with its title in the light. Motion
(`hypr/lua/animations.lua`) glides and settles with no overshoot; layers only (`hypr/lua/animations.lua`) glides and settles with no overshoot: windows
fade, since quickshell animates its own surfaces. resolve in from a hair smaller, workspaces drift a short way vertically and
fade (sliding behind the hull), the `SUPER+S` scratchpad lowers out from
under the bar like the dashboard, and layers only fade, since quickshell
animates its own surfaces.
- **Alacritty** — `alacritty/yorha.toml`, selected by the `import` in - **Alacritty** — `alacritty/yorha.toml`, selected by the `import` in
`alacritty.toml`. Muted earth hues on the void; selection inverts. `alacritty.toml`. Muted earth hues on the void; selection inverts.
Alacritty only live-reloads when `alacritty.toml` itself changes, so touch Alacritty only live-reloads when `alacritty.toml` itself changes, so touch
@@ -337,9 +340,13 @@ still exists as a no-op property so old call sites need not be chased down.
**The hull.** Borrowed from caelestia's screen border: the bar, the rail and a **The hull.** Borrowed from caelestia's screen border: the bar, the rail and a
thin band down the left and along the bottom (`Theme.frame`) are one surface, thin band down the left and along the bottom (`Theme.frame`) are one surface,
drawn by `bar/Frame.qml` on the bottom layer, and the desktop is a rounded drawn by `bar/Frame.qml`, and the desktop is a rounded opening cut out of it
opening cut out of it (`Theme.frameRadius`). Read against the wallpaper, you are (`Theme.frameRadius`). Read against the wallpaper, you are inside the ship and
inside the ship and the windows sit in the porthole. The bar and the rail draw the windows sit in the porthole. The hull is on the top layer, over the
windows, with a transparent opening and no input region, so a window moving
past the opening's edge — a workspace switch, the scratchpad drawer — passes
behind the frame rather than over the bar. `hypr/lua/rules/layers.lua` orders
the bar and the rail above it. The bar and the rail draw
no surface of their own and ignore exclusive zones; Frame reserves all four no surface of their own and ignore exclusive zones; Frame reserves all four
zones itself, so every piece is placed from the screen edge. zones itself, so every piece is placed from the screen edge.
+8 -5
View File
@@ -9,10 +9,13 @@ import "root:/config"
// The idea is caelestia's screen border; the reading is the wallpaper's — you // The idea is caelestia's screen border; the reading is the wallpaper's — you
// are inside the ship, and the windows sit in the porthole. // are inside the ship, and the windows sit in the porthole.
// //
// The hull lives on the bottom layer, under every window. Hyprland's gaps keep // The hull lives on the top layer, over every window, and its opening is
// tiled windows inside the opening, so nothing ever has to be drawn over a // transparent with no input region, so it covers nothing inside the opening
// window; a floating window dragged onto the frame simply covers it, and a // and catches no clicks. Being above the windows is the point: a workspace
// fullscreen one hides it, which is what should happen in both cases. // switch slides windows *behind* the frame, the way things pass behind the
// rim of a porthole, instead of drawing over the bar. A fullscreen window
// still covers it, since Hyprland draws fullscreen above the top layer.
// hypr/lua/rules/layers.lua orders it under the bar and the rail.
// //
// The bar and the rail draw no surface of their own and ignore exclusive // The bar and the rail draw no surface of their own and ignore exclusive
// zones, so every piece is placed from the screen edge. The four zones are // zones, so every piece is placed from the screen edge. The four zones are
@@ -29,7 +32,7 @@ Scope {
screen: root.modelData screen: root.modelData
WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "takemi-frame" WlrLayershell.namespace: "takemi-frame"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore