Files

141 lines
4.9 KiB
QML

// Window mode: outline every window on the focused workspace, highlight the one
// under the pointer and dim the rest, click to capture it.
//
// Geometry comes from `hyprctl -j clients` via services/Capture.qml, because
// Quickshell's HyprlandToplevel exposes no position or size.
import QtQuick
import qs.config
import qs.services
Item {
id: root
// Layout-global origin of the output this overlay covers.
property real originX: 0
property real originY: 0
signal picked
readonly property var windows: Capture.windows
// Front-to-back hit test: Capture.windows is already sorted by
// focusHistoryID, so the first hit is the topmost window.
readonly property int hoveredIndex: {
if (!area.containsMouse)
return -1;
const mx = area.mouseX;
const my = area.mouseY;
for (let i = 0; i < root.windows.length; i++) {
const w = root.windows[i];
const x = w.x - root.originX;
const y = w.y - root.originY;
if (mx >= x && mx <= x + w.w && my >= y && my <= y + w.h)
return i;
}
return -1;
}
// Once a window has been hovered it stays highlighted after the pointer
// moves down onto the control bar, so the shutter button always has
// something to shoot. Index 0 is the most recently focused window, which is
// what GNOME preselects.
property int stickyIndex: 0
onHoveredIndexChanged: {
if (root.hoveredIndex >= 0)
root.stickyIndex = root.hoveredIndex;
}
onWindowsChanged: root.stickyIndex = 0
readonly property int activeIndex: root.hoveredIndex >= 0 ? root.hoveredIndex : root.stickyIndex
readonly property var hovered: (root.activeIndex >= 0 && root.activeIndex < root.windows.length) ? root.windows[root.activeIndex] : null
// Layout-global rect of the hovered window, or null. The overlay hands this
// straight to Capture.commit().
readonly property var selectedRect: root.hovered ? Qt.rect(root.hovered.x, root.hovered.y, root.hovered.w, root.hovered.h) : null
Spotlight {
anchors.fill: parent
holeVisible: root.hovered !== null
hole: root.hovered ? Qt.rect(root.hovered.x - root.originX, root.hovered.y - root.originY, root.hovered.w, root.hovered.h) : Qt.rect(0, 0, 0, 0)
}
// Faint outline on every candidate so the user can see what is pickable
// before moving the pointer, the way GNOME's window shots do.
Repeater {
model: root.windows
Frame {
required property var modelData
required property int index
x: modelData.x - root.originX
y: modelData.y - root.originY
width: modelData.w
height: modelData.h
visible: index !== root.activeIndex
strokeWidth: 1
strokeColor: Theme.alpha(Theme.fg, 0.35)
}
}
Frame {
visible: root.hovered !== null
x: root.hovered ? root.hovered.x - root.originX : 0
y: root.hovered ? root.hovered.y - root.originY : 0
width: root.hovered ? root.hovered.w : 0
height: root.hovered ? root.hovered.h : 0
strokeWidth: 3
strokeColor: Theme.accent
fillColor: Theme.alpha(Theme.accent, 0.08)
}
// Title chip on the highlighted window — stacked windows are otherwise very
// hard to tell apart once the rest of the screen is dimmed.
Rectangle {
id: chip
visible: root.hovered !== null
x: (root.hovered ? root.hovered.x - root.originX : 0) + 12
y: (root.hovered ? root.hovered.y - root.originY : 0) + 12
implicitWidth: Math.min(chipText.implicitWidth + 24, root.hovered ? root.hovered.w - 24 : 0)
implicitHeight: 32
radius: Theme.pillRadius
border.width: 0
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
Text {
id: chipText
anchors.centerIn: parent
width: chip.width - 24
text: root.hovered ? (root.hovered.title || root.hovered.appId) : ""
color: Theme.fg
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
// Shown when hyprctl gave us nothing — a blank dimmed screen with no
// explanation would just look broken.
Text {
anchors.centerIn: parent
visible: root.windows.length === 0
text: "No windows to capture on this workspace"
color: Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
}
MouseArea {
id: area
anchors.fill: parent
hoverEnabled: true
cursorShape: root.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (root.hovered)
root.picked();
}
}
}