import QtQuick import qs.Common import qs.Widgets Item { id: win property var panel: null property string widgetId: "" property string title: "" property string icon: "" property real widgetWidth: 360 property real widgetHeight: 320 property real minimumY: 0 property bool shown: false property Component contentComponent: null readonly property real margin: panel ? panel.edgeMargin : 12 readonly property real panelWidth: panel ? panel.width : 0 readonly property real panelHeight: panel ? panel.height : 0 readonly property real maxX: Math.max(margin, (panel ? panel.width : width) - width - margin) readonly property real maxY: Math.max(minimumY, (panel ? panel.height : height) - height - margin) function clampX(value) { return Math.max(margin, Math.min(maxX, value)); } function clampY(value) { return Math.max(minimumY, Math.min(maxY, value)); } // Positions are stored as fractions of the screen, so a layout made on a 4K // display still lands somewhere sensible on a 1440p one (and vice versa). function applyStoredPosition() { if (!panel || panel.width <= 0 || panel.height <= 0) return; const stored = panel.widgetPosition(win.widgetId); win.x = win.clampX(stored.x * panel.width); win.y = win.clampY(stored.y * panel.height); } function raise() { if (panel) win.z = panel.nextWindowZ(); } function storePosition() { if (!panel || panel.width <= 0 || panel.height <= 0) return; panel.saveWidgetPosition(win.widgetId, win.x / panel.width, win.y / panel.height); } width: widgetWidth height: widgetHeight visible: shown && opacity > 0 opacity: shown ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic } } Component.onCompleted: win.applyStoredPosition() onShownChanged: { if (shown) { win.applyStoredPosition(); win.raise(); } } // The panel has no size yet while the delegate is being created, so the // stored fraction has to be re-applied once the screen size is known. onPanelWidthChanged: win.applyStoredPosition() onPanelHeightChanged: win.applyStoredPosition() Rectangle { id: frame anchors.fill: parent radius: Theme.cornerRadius * 1.25 color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) border.color: dragArea.pressed ? Theme.primary : Theme.outlineMedium border.width: dragArea.pressed ? 2 : Theme.layerOutlineWidth Rectangle { id: titleBar anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: 38 radius: frame.radius color: dragArea.containsMouse ? Theme.surfaceContainerHigh : "transparent" // Square off the bottom corners so the title bar blends into the body. Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: parent.radius color: parent.color } DankIcon { id: titleIcon anchors.left: parent.left anchors.leftMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter name: win.icon size: Theme.iconSize - 6 color: Theme.primary } StyledText { anchors.left: titleIcon.right anchors.leftMargin: Theme.spacingS anchors.right: closeButton.left anchors.rightMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter text: win.title font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText elide: Text.ElideRight } DankActionButton { id: closeButton anchors.right: parent.right anchors.rightMargin: Theme.spacingXS anchors.verticalCenter: parent.verticalCenter iconName: "close" buttonSize: 26 iconSize: 15 iconColor: Theme.surfaceVariantText onClicked: { if (win.panel) win.panel.toggleWidget(win.widgetId); } } MouseArea { id: dragArea anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: closeButton.left hoverEnabled: true cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor drag.target: win drag.axis: Drag.XAndYAxis drag.minimumX: win.margin drag.maximumX: win.maxX drag.minimumY: win.minimumY drag.maximumY: win.maxY drag.threshold: 0 onPressed: win.raise() onReleased: win.storePosition() } } MouseArea { anchors.fill: parent acceptedButtons: Qt.AllButtons propagateComposedEvents: true z: -1 onPressed: mouse => { win.raise(); mouse.accepted = false; } } Loader { anchors.top: titleBar.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.leftMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM anchors.bottomMargin: Theme.spacingM active: win.shown sourceComponent: win.contentComponent } } }