colour -> color, behaviour -> behavior, centre -> center, favourite -> favorite, and about twenty other pairs, applied consistently across comments, docs, error/UI copy, and a handful of QML identifiers that used the British spelling as their actual name: SystemSettings' serialiseValue/serialiseTable/normaliseGradient, Displays' normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's _normalise helper, and ShortcutCapture's cancelled signal (with its onCancelled handler in ShortcutsPage.qml). Every call site and the two tests that assert on the literal source text (settings-ownership and settings-backup-live contracts) were updated in lockstep. Left untouched: config/dot/espanso/match/packages/misspell-en/ is a vendored third-party autocorrect dictionary -- its entries are typo corrections, not our prose, and rewriting them would fight the package's own purpose (and any future re-sync from upstream). The already-American `favorites` property (Home page pinned accessories) was never actually misspelled -- only nearby comments and error strings said "favourites" -- so no data migration was needed there. Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
183 lines
6.2 KiB
QML
183 lines
6.2 KiB
QML
// One workspace in the overview: a proportional card holding live thumbnails
|
|
// of its windows, labeled underneath. The focused workspace is outlined in the
|
|
// accent color, 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
|
|
}
|
|
}
|