Files
Panama/config/dot/quickshell/modules/overview/WorkspaceCard.qml
T

183 lines
6.2 KiB
QML

// One workspace in the overview: a proportional card holding live thumbnails
// of its windows, labelled underneath. The focused workspace is outlined in the
// accent colour, as GNOME outlines the current workspace.
//
// A card with a null `workspace` is the trailing "new workspace" affordance
// that dynamic-workspace setups (GNOME's, and Hyprland's) always show.
import Quickshell
import QtQuick
import qs.config
Item {
id: root
// HyprlandWorkspace, or null for the trailing "new workspace" card.
required property var workspace
// Shown on the empty trailing card.
property int newWorkspaceId: 0
property int refreshToken: 0
// `emphasized` is the overview's current spatial selection. It normally
// follows compositor focus, but can point at a requested focus workspace
// before the user commits to switching there.
property bool emphasized: focused
property bool focusBound: false
signal activated
signal windowActivated(var toplevel)
signal windowCloseRequested(var toplevel)
signal windowDropped(var toplevel, string workspaceName)
readonly property bool isNew: !workspace
readonly property var windows: workspace ? workspace.toplevels.values : []
readonly property bool focused: workspace ? workspace.focused : false
readonly property string dropWorkspaceName: workspace ? String(workspace.id)
: (newWorkspaceId > 0 ? String(newWorkspaceId) : "")
readonly property string label: {
if (isNew)
return qsTr("New");
// Named workspaces keep their name; numbered ones read better as
// "Workspace N" only when there is room, so just show the name.
return workspace.name ? workspace.name : String(workspace.id);
}
// Card body — the label sits under it, so reserve that space.
readonly property int labelHeight: 22
Rectangle {
id: card
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: root.labelHeight
radius: Theme.cardRadius
color: Theme.alpha(Theme.bgDark, 0.55)
border.width: 2
border.color: {
if (dropArea.containsDrag)
return Theme.ok;
if (root.emphasized)
return Theme.accent;
if (root.focusBound)
return Theme.alpha(Theme.accentSecondary, 0.72);
return Theme.alpha(Theme.fg, 0.08);
}
Behavior on border.color {
ColorAnimation {
duration: Theme.durFast
}
}
// Windows are laid out on a near-square grid. Hyprland does not expose
// live per-window geometry, so an honest grid beats a wrong guess at
// the real tiling.
readonly property int columns: Math.max(1, Math.ceil(Math.sqrt(root.windows.length)))
readonly property int rows: Math.max(1, Math.ceil(root.windows.length / columns))
readonly property int cellW: Math.floor((width - Theme.itemSpacing * (columns + 1)) / columns)
readonly property int cellH: Math.floor((height - Theme.itemSpacing * (rows + 1)) / rows)
Grid {
anchors.centerIn: parent
columns: card.columns
spacing: Theme.itemSpacing
visible: root.windows.length > 0
Repeater {
model: root.windows
WindowThumbnail {
required property var modelData
toplevel: modelData
refreshToken: root.refreshToken
width: card.cellW
height: card.cellH
onActivated: root.windowActivated(modelData)
onCloseRequested: root.windowCloseRequested(modelData)
}
}
}
DropArea {
id: dropArea
anchors.fill: parent
z: 3
enabled: root.dropWorkspaceName !== ""
onDropped: drop => {
if (!drop.source?.toplevel)
return;
root.windowDropped(drop.source.toplevel, root.dropWorkspaceName);
drop.acceptProposedAction();
}
}
// Empty workspace: a big soft "+" so the card still reads as clickable.
Text {
anchors.centerIn: parent
visible: root.windows.length === 0
text: "+"
color: Theme.alpha(Theme.fg, 0.25)
font.family: Theme.fontFamily
font.pixelSize: Math.max(Theme.fontSizeTitle, Math.round(card.height * 0.28))
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 8
visible: root.focusBound
width: focusBadge.implicitWidth + 16
height: 24
radius: Theme.pillRadius
border.width: 0
color: Theme.alpha(Theme.bgDark, 0.82)
Row {
id: focusBadge
anchors.centerIn: parent
spacing: 5
Text {
anchors.verticalCenter: parent.verticalCenter
text: "\u{F051F}"
color: Theme.accent
font.family: Theme.fontMono
font.pixelSize: 12
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Focus"
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
}
}
// Sits below the thumbnails so clicking a window still focuses it.
MouseArea {
anchors.fill: parent
z: -1
onClicked: root.activated()
}
}
Text {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: root.labelHeight
verticalAlignment: Text.AlignVCenter
text: root.label
color: root.emphasized ? Theme.accent : Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
font.bold: root.emphasized
}
}