Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user