Files
dotfiles/quickshell/overlays/Toasts.qml
T
nvrlandClaude Opus 5.5 ae6539218e theme: strip the remaining Persona punch from Hyprland and the popouts
Hyprland:
- Windows round at 8px, concentric with the porthole's 18px corners at
  the 10px gap, instead of square corners inside a rounded opening.
- Group tabs are solid hull slabs; the titles used to float over the
  wallpaper. The current tab is lifted a step with its title in the light.
- Motion rewritten to glide and settle: no overshoot curve, gentler
  popin, workspaces slide-fade a short way instead of sweeping the whole
  screen, and layers only fade so they never stack on quickshell's own
  slides.

Quickshell:
- Album art in the bar and the media popout is square in a hairline frame,
  not masked to a leaning chamfered shape; the accent slab under the
  popout art is gone, and a track change dips the title instead of
  tilting the art.
- The bar's equaliser is a five-hairline signal meter that rests as dots.
- Segment meters (system, battery, OSD) are 4px rows instead of 10-12px
  blocks; the toast fuse is a hairline; the clock's accent slab is a
  neutral divider.
- No hover pops: rail icons, tray icons, media transport buttons and
  P5Button no longer scale up or lunge sideways; the icon bump is reduced.

Terminal: ANSI bright black drops to #464d44. TUIs use it as a panel fill,
and at the outline grey it read as a flat light slab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:45:24 +02:00

155 lines
4.8 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
import "root:/config"
import "root:/components"
import "root:/services"
// Floating notification stack under the bar, top right. Replaces dunst.
PanelWindow {
id: root
required property var modelData
screen: modelData
// A toast window exists on every screen, but each notification lands only
// on the monitor that had focus when it arrived. Latched per toast rather
// than live, so a card does not hop screens while it is being read.
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "takemi-toasts"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors { top: true; right: true }
margins {
top: Theme.barHeight + Theme.frame
right: Theme.railWidth + Theme.frame
}
implicitWidth: 400
implicitHeight: Math.max(1, stack.implicitHeight)
color: "transparent"
exclusionMode: ExclusionMode.Ignore
visible: root.live.length > 0
// Toasts currently on screen, newest first.
property list<Notification> live: []
property int maxLive: 5
Connections {
target: Notifs
function onPopupRequested(notif) {
if (Actions.focusedScreenName() !== root.modelData.name) return;
root.live = [notif, ...root.live].slice(0, root.maxLive);
}
}
function retire(notif) {
const idx = root.live.indexOf(notif);
if (idx === -1) return;
const next = root.live.slice();
next.splice(idx, 1);
root.live = next;
}
Column {
id: stack
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
spacing: Theme.padS
Repeater {
model: root.live
Item {
id: toast
required property var modelData
width: stack.width
height: card.implicitHeight
NotifCard {
id: card
width: parent.width
notif: toast.modelData
onDismissed: {
Notifs.dismiss(toast.modelData);
root.retire(toast.modelData);
}
}
// Countdown hairline that drains along the bottom edge. The
// chop and lean terms are zero under the YoRHa theme; they are
// kept so the fuse still fits if a shape is brought back.
readonly property real fuseSpan: Math.max(0,
width - card.chop - card.implicitHeight * card.lean)
Skew {
id: fuse
anchors.bottom: parent.bottom
anchors.bottomMargin: 1
x: card.chop
height: 1
width: toast.fuseSpan
lean: card.lean
color: Theme.alpha(card.tone, 0.7)
visible: life.running
}
NumberAnimation {
id: life
target: fuse
property: "width"
from: toast.fuseSpan
to: 0
running: false
duration: Notifs.timeoutFor(toast.modelData)
easing.type: Easing.Linear
onFinished: root.retire(toast.modelData)
}
// Slide in from the right.
opacity: 0
transform: Translate { id: shove; x: 60 }
Component.onCompleted: {
enter.start();
const ms = Notifs.timeoutFor(toast.modelData);
if (ms > 0) life.start();
}
ParallelAnimation {
id: enter
NumberAnimation {
target: toast; property: "opacity"
to: 1; duration: Theme.durBase
}
NumberAnimation {
target: shove; property: "x"
from: 60; to: 0
duration: Theme.durSlow
easing.type: Easing.OutCubic
}
}
// Hovering a toast pauses its fuse.
HoverHandler {
id: toastHover
onHoveredChanged: {
if (!life.duration) return;
if (hovered) life.pause();
else if (life.paused) life.resume();
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: root.retire(toast.modelData)
}
}
}
}
}