// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import Quickshell.Wayland import Quickshell.Widgets import QtQuick Column { id: card required property var toplevel property bool selected: false property int cardWidth: 380 signal activated() signal closeRequested() // A Hyprland toplevel carries its Wayland handle separately, and the two // are not created in the same instant, so this reads null for a window // that is appearing or going away. Everything below guards for it. readonly property var wl: toplevel.wayland ?? null width: cardWidth spacing: 8 // A fixed 16:10 box with the preview fitted inside it. The previews are // not one shape: DP-1 windows are near 21:9 and DP-3 is rotated, so its // windows are portrait. Sizing each card to its own preview would give // ragged rows and break the alignment of the text lines below. Rectangle { id: box width: card.cardWidth height: Math.round(card.cardWidth * 10 / 16) radius: 10 color: Theme.surface border.color: card.selected ? Theme.accent : Theme.surfaceAlt border.width: card.selected ? 3 : 1 ScreencopyView { id: preview anchors.centerIn: parent captureSource: card.wl live: true // An uncaptured view reports sourceSize (-1, -1) rather than // (0, 0), so this tests for a positive height rather than a // non-zero one, and falls back to the box's own ratio. readonly property real ar: sourceSize.height > 0 ? sourceSize.width / sourceSize.height : 16 / 10 width: Math.min(parent.width - 8, (parent.height - 8) * ar) height: Math.min(parent.height - 8, (parent.width - 8) / ar) } MouseArea { anchors.fill: parent onClicked: card.activated() } // Both corner overlays sit on an arbitrary window preview, so each // carries its own scrim: a themed icon can otherwise land on a // same-coloured region and vanish, a light icon on a white page being // the obvious case. Rectangle { anchors { left: parent.left; top: parent.top; margins: 8 } width: 34; height: 34; radius: 8 color: Qt.rgba(Theme.base.r, Theme.base.g, Theme.base.b, 0.75) IconImage { anchors.centerIn: parent width: 24; height: 24 source: Quickshell.iconPath(card.wl ? card.wl.appId : "", true) } } Rectangle { id: closeButton anchors { right: parent.right; top: parent.top; margins: 8 } width: 34; height: 34; radius: 17 color: closeHover.hovered ? Theme.red : Qt.rgba(Theme.base.r, Theme.base.g, Theme.base.b, 0.75) IconImage { anchors.centerIn: parent width: 22; height: 22 source: Quickshell.iconPath("window-close", true) } HoverHandler { id: closeHover } MouseArea { anchors.fill: parent onClicked: card.closeRequested() } } } Text { width: parent.width horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight text: card.wl ? card.wl.appId : "" color: Theme.text font.family: Theme.fontFamily font.pixelSize: 16 font.bold: true } Text { width: parent.width elide: Text.ElideRight text: card.toplevel.title color: Theme.subtext font.family: Theme.fontFamily font.pixelSize: 13 } Text { width: parent.width horizontalAlignment: Text.AlignHCenter text: "[" + (card.toplevel.workspace ? card.toplevel.workspace.name : "?") + "]" color: Theme.accent font.family: Theme.fontFamily font.pixelSize: 13 } }