import QtQuick import Quickshell import Quickshell.Wayland import "." as M // Shared hover/OSD panel PanelWindow — slides down from the bar on hover or // external trigger. Parent module computes showPanel and reads panelHovered. PanelWindow { id: root required property bool showPanel required property Item anchorItem required property color accentColor property string panelNamespace: "nova-panel" property real contentWidth: 220 property bool animateHeight: false property bool panelHovered: false default property alias content: panelContent.children visible: _winVisible color: "transparent" property bool _winVisible: false WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.exclusiveZone: 0 WlrLayershell.namespace: root.panelNamespace anchors.top: true anchors.left: true margins.top: 0 implicitWidth: panelContent.width implicitHeight: panelContent.height Behavior on implicitHeight { enabled: root.animateHeight NumberAnimation { duration: 100 easing.type: Easing.OutCubic } } function _updatePosition() { const pt = anchorItem.mapToGlobal(anchorItem.width / 2, 0); const scr = screen; const sw = scr?.width ?? 1920; margins.left = Math.max(0, Math.min(Math.round(pt.x - (scr?.x ?? 0) - contentWidth / 2), sw - contentWidth)); } Timer { id: _hideTimer interval: 50 onTriggered: { if (!root.showPanel) { showAnim.stop(); hideAnim.start(); } } } onShowPanelChanged: { if (showPanel) { _hideTimer.stop(); _updatePosition(); _winVisible = true; hideAnim.stop(); showAnim.start(); } else { _hideTimer.restart(); } } ParallelAnimation { id: showAnim NumberAnimation { target: panelContent property: "opacity" to: 1 duration: 120 easing.type: Easing.OutCubic } NumberAnimation { target: panelContent property: "y" to: 0 duration: 150 easing.type: Easing.OutCubic } } ParallelAnimation { id: hideAnim NumberAnimation { target: panelContent property: "opacity" to: 0 duration: 150 easing.type: Easing.InCubic } NumberAnimation { target: panelContent property: "y" to: -panelContent.height duration: 150 easing.type: Easing.InCubic } onFinished: root._winVisible = false } HoverHandler { onHoveredChanged: { console.log("[hoverpanel:" + root.panelNamespace + "] hovered →", hovered); root.panelHovered = hovered; } } M.PopupBackground { x: panelContent.x y: panelContent.y width: panelContent.width height: panelContent.height opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85) accentColor: root.accentColor } Column { id: panelContent width: root.contentWidth opacity: 0 y: -height } // Border overlay — drawn on top of content so full-bleed items (e.g. album art) don't cover it Rectangle { x: panelContent.x y: panelContent.y width: panelContent.width height: panelContent.height color: "transparent" border.color: root.accentColor border.width: 1 bottomLeftRadius: M.Theme.radius bottomRightRadius: M.Theme.radius opacity: panelContent.opacity } }