Files
att_menu/modules/WindowButton.qml
2026-07-26 21:16:55 +02:00

111 lines
3.6 KiB
QML

import Quickshell
import QtQuick
import qs.config
import qs.components
import qs.services
// Left click focuses the window, or minimises it if it is already focused —
// except under attwm, which has no minimised state. Middle click closes it.
PanelButton {
id: root
// The window this button stands for: either an attwm window object from its
// IPC state, or a Toplevel handle from wlr-foreign-toplevel-management.
// Both carry the same facts under different names, so `attwm` says which is
// in hand rather than each read having to guess.
required property var win
required property bool attwm
readonly property string appId: (root.attwm ? root.win.app_id : root.win.appId) || ""
readonly property bool activated: root.attwm ? root.win.focused : root.win.activated
// Not on screen right now: minimised elsewhere, and under attwm parked on a
// tag nobody is viewing, which is that model's equivalent.
readonly property bool hidden: root.attwm ? !root.win.visible : root.win.minimized
// Desktop entries carry a proper themed icon; appId alone often works as an
// icon name too, which is the fallback AppIcon ends up using.
readonly property var entry: DesktopEntries.heuristicLookup(root.appId)
readonly property string title: root.win.title || root.appId || "Window"
active: root.activated
minContentWidth: Config.windowButtonMinWidth
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
opacity: root.hidden ? 0.55 : 1
Behavior on opacity {
NumberAnimation {
duration: Theme.animDuration
easing.type: Theme.animEasing
}
}
onClicked: event => {
if (event.button === Qt.MiddleButton) {
if (root.attwm)
Attwm.closeWindow(root.win.id);
else
root.win.close();
return;
}
if (root.attwm) {
// attwm has nothing to minimise into — a window is on a tag or it
// is not — so a click always means focus, and attwm brings the
// window's tags into view if they are not already.
Attwm.focusWindow(root.win.id);
return;
}
if (root.win.activated) {
root.win.minimized = true;
} else {
root.win.minimized = false;
root.win.activate();
}
}
// Both children are given the same height so the Row needs no anchors,
// which positioners disallow.
Row {
spacing: Theme.spacing + 2
AppIcon {
icon: (root.entry && root.entry.icon) || root.appId
fallbackLabel: root.appId || root.title
size: Theme.iconSize
}
Text {
id: label
// Clamp to the natural width so the button never depends on its own
// width, which would loop through PanelButton's implicit sizing.
width: Math.min(implicitWidth, Config.windowButtonMaxWidth)
height: Theme.iconSize
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
text: root.title
color: root.activated ? Theme.text : Theme.textDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
}
}
// Active-window underline, the usual taskbar affordance.
overlay: Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: Theme.radius
rightMargin: Theme.radius
}
height: 2
radius: 1
color: Theme.accent
visible: root.activated
}
}