Desktop & Dock becomes Shell — Bar, Dock, Control Center, Tiling, Workspaces — the home for everything Quickshell draws. The settings- management cluster moves to System as Sync & Backup, Appearance's Shell tab dissolves, and 24-hour time finally lives on Date & Time, which always owned it. The bar gets what it never had: a way to survive the wallpaper. A second neutral text family (follow theme, or forced light or dark), a one-layer shadow under every glyph, and a gradient scrim for wallpapers nothing else survives — all off by default, pixel-identical until asked. Widgets earn toggles (weather, media, clipboard, calendar countdown), the vitals cluster stops leaving a dead pill behind, and Control Center's sections learn to step aside. The dock graduates from MVP: a context menu with window rows, pin, unpin, quit and new-window; scroll an icon to cycle its windows; drag to reorder on the dock itself; hover previews with one-shot captures; and "Add App to Dock" in the launcher. Three real bugs died en route — menus that slid away with the autohide, a readonly-property crash on every menu open, and a drag that drifted half a slot per icon on side docks. The pinned-apps editor in Settings becomes a drag strip. 166 contracts; the full suite is green except two live display and switcher tests that cannot run behind a locked session — re-verified on unlock. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
74 lines
2.5 KiB
QML
74 lines
2.5 KiB
QML
// Now-playing readout. Click toggles play/pause, scroll skips tracks.
|
|
//
|
|
// "Nothing playing" means no MPRIS player is exposing a track at all — a
|
|
// paused player still shows, because otherwise there would be nothing left on
|
|
// the bar to press play on.
|
|
|
|
import QtQuick
|
|
import Quickshell.Services.Mpris
|
|
import qs.config
|
|
import qs.services
|
|
import qs.widgets
|
|
|
|
Pill {
|
|
id: root
|
|
|
|
// Prefer whatever is actually playing; fall back to the first player that
|
|
// has a track loaded so a paused session stays reachable. The model's
|
|
// insertion behavior is covered by the live-player verification.
|
|
readonly property MprisPlayer player: {
|
|
const players = Mpris.players ? Mpris.players.values : [];
|
|
return players.find(p => p.isPlaying) ?? players.find(p => p.trackTitle) ?? null;
|
|
}
|
|
|
|
readonly property string label: {
|
|
if (!root.player)
|
|
return "";
|
|
const title = root.player.trackTitle ?? "";
|
|
const artist = root.player.trackArtist ?? "";
|
|
return artist ? artist + " — " + title : title;
|
|
}
|
|
|
|
visible: Settings.showMediaWidget && root.player !== null
|
|
horizontalPadding: 8
|
|
|
|
onActivated: if (root.player?.canTogglePlaying)
|
|
root.player.togglePlaying()
|
|
|
|
// Right-click opens the settings that govern this widget. Output device and per-application volume.
|
|
onSecondaryActivated: ShellState.openSettings("sound")
|
|
|
|
// Scroll up = previous, down = next — the same direction as the workspace
|
|
// switcher, so the whole bar scrolls consistently.
|
|
onScrolled: delta => {
|
|
if (!root.player)
|
|
return;
|
|
if (delta > 0 && root.player.canGoPrevious)
|
|
root.player.previous();
|
|
else if (delta < 0 && root.player.canGoNext)
|
|
root.player.next();
|
|
}
|
|
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
// Shows the action the click will perform, not the current state.
|
|
text: root.player?.isPlaying ? "\u{F04C}" : "\u{F04B}" // fa-pause / fa-play
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.accent
|
|
}
|
|
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.label
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
color: Theme.barFg
|
|
|
|
// Titles are unbounded; the bar is not. Elide rather than let one
|
|
// podcast episode push the tray off the edge.
|
|
elide: Text.ElideRight
|
|
width: Math.min(implicitWidth, 200)
|
|
}
|
|
}
|