pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Hyprland import qs.widgets Scope { id: root required property var bar property real gaps: 5 property Item parentItem property PopupItem activeItem property PopupItem lastActiveItem property PopupItem shownItem: activeItem ?? lastActiveItem onActiveItemChanged: { if (activeItem != null) { activeItem.targetVisible = true; if (parentItem) { activeItem.parent = parentItem; } } if (lastActiveItem != null && lastActiveItem != activeItem) { lastActiveItem.targetVisible = false; } if (activeItem != null) { lastActiveItem = activeItem; } } function setItem(item: PopupItem) { activeItem = item; } function removeItem(item: PopupItem) { if (activeItem == item) { activeItem = null; } } function onHidden(item: PopupItem) { if (item == lastActiveItem) { console.log("triggered"); lastActiveItem = null; } } property real scaleMul: lastActiveItem && lastActiveItem.targetVisible ? 1 : 0 Behavior on scaleMul { SmoothedAnimation { velocity: 5 } } LazyLoader { id: popupLoader active: root.shownItem != null PopupWindow { id: popup visible: true color: "transparent" implicitWidth: root.bar.width implicitHeight: Math.max(800, parentItem.targetHeight) anchor { window: root.bar rect: Qt.rect(0, 0, root.bar.width, root.bar.height) edges: Edges.Bottom | Edges.Left gravity: Edges.Bottom | Edges.Right adjustment: PopupAdjustment.None } mask: Region { item: parentItem } HyprlandFocusGrab { id: grab active: true windows: [popup, root.bar] onCleared: { if (!active) { root.shownItem.closed(); } } } // HyprlandWindow.opacity: root.scaleMul HyprlandWindow.visibleMask: popup.mask Connections { target: root function onScaleMulChanged() { popup.mask.changed(); } } StyledRectangle { id: parentItem width: targetWidth height: targetHeight x: targetX y: root.gaps transform: Scale { origin.x: parentItem.targetX origin.y: 0 xScale: 1 yScale: root.scaleMul } readonly property var targetWidth: root.shownItem?.implicitWidth ?? 0 readonly property var targetHeight: root.shownItem?.implicitHeight ?? 0 readonly property var targetX: { if (root.shownItem == null) { return 0; } let owner = root.shownItem.owner; let bar = root.bar; let isCentered = root.shownItem.centered; let xPos = owner.mapToItem(bar.contentItem, 0, bar.height, owner.width, 0).x; let rightEdge = xPos + targetWidth; let maxRightEdge = popup.width; if (isCentered) { return xPos - (targetWidth / 2) + (owner.width / 2); } if (rightEdge > maxRightEdge) { // touching right edge, reposition // console.log("touching right edge"); return maxRightEdge - targetWidth - root.gaps; } return xPos; } Component.onCompleted: { root.parentItem = this; if (root.activeItem) { root.activeItem.parent = this; } } // TODO: Make a close animation, a little complicated, will need to track if an animation is running // and stop unload from occuring until its done, in the LazyLoader. Behavior on x { enabled: root.lastActiveItem != null SmoothedAnimation { duration: 200 easing.type: Easing.InOutQuad } } Behavior on width { enabled: root.lastActiveItem != null SmoothedAnimation { duration: 200 easing.type: Easing.InOutQuad } } Behavior on height { enabled: root.lastActiveItem != null SmoothedAnimation { duration: 200 easing.type: Easing.InOutQuad } } // SmoothedAnimation on height { // duration: 200 // easing.type: Easing.InOutQuad // to: parentItem.targetHeight // onToChanged: restart() // } } } } }