aboutsummaryrefslogtreecommitdiffstats
path: root/window-switcher/WindowCard.qml
diff options
context:
space:
mode:
Diffstat (limited to 'window-switcher/WindowCard.qml')
-rw-r--r--window-switcher/WindowCard.qml136
1 files changed, 136 insertions, 0 deletions
diff --git a/window-switcher/WindowCard.qml b/window-switcher/WindowCard.qml
new file mode 100644
index 0000000..de026e8
--- /dev/null
+++ b/window-switcher/WindowCard.qml
@@ -0,0 +1,136 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// 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
+ }
+}