// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtWayland.Compositor Item { id: chrome property bool positionSet: false x: shellSurface.windowGeometry.x - leftResizeHandle.width y: shellSurface.windowGeometry.y - topResizeHandle.height - titleBar.height width: shellSurface.windowGeometry.width + leftResizeHandle.width + rightResizeHandle.width height: shellSurface.windowGeometry.height + topResizeHandle.height + titleBar.height + bottomResizeHandle.height property rect oldGeometry: Qt.rect(0, 0, 100, 100) property bool isChild: parent.shellSurface !== undefined property alias shellSurface: shellSurfaceItem.shellSurface property int windowState: Qt.WindowNoState signal destroyAnimationFinished signal activated signal deactivated property int windowFlags: shellSurface.windowFlags !== Qt.Window ? shellSurface.windowFlags : defaultFlags onDecorationsShowingChanged:{ shellSurfaceItem.updateFrameMargins() } Component.onCompleted: { shellSurface.active = true } property int defaultFlags: (Qt.Window | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) property bool frameless: (chrome.windowFlags & Qt.FramelessWindowHint) != 0 || (chrome.windowState & Qt.WindowFullScreen) != 0 || ((chrome.windowFlags & Qt.Popup) == Qt.Popup && (chrome.windowFlags & Qt.Tool) != Qt.Tool) property bool decorationsShowing: (chrome.windowFlags & Qt.Window) != 0 && !frameless transform: [ Scale { id: scaleTransform origin.x: chrome.width / 2 origin.y: chrome.height / 2 } ] Rectangle { id: leftResizeHandle color: "gray" width: visible ? 5 : 0 anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: westBound } } Rectangle { id: rightResizeHandle color: "gray" width: visible ? 5 : 0 anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: eastBound } } Rectangle { id: topResizeHandle color: "gray" height: visible ? 5 : 0 anchors.left: parent.left anchors.top: parent.top anchors.right: parent.right visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: northBound } } Rectangle { id: bottomResizeHandle color: "gray" height: visible ? 5 : 0 anchors.left: parent.left anchors.bottom: parent.bottom anchors.right: parent.right visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: southBound } } Rectangle { id: topLeftResizeHandle color: "gray" height: 5 width: 5 anchors.left: parent.left anchors.top: parent.top visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: westBound | northBound } } Rectangle { id: topRightResizeHandle color: "gray" height: 5 width: 5 anchors.right: parent.right anchors.top: parent.top visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: eastBound | northBound } } Rectangle { id: bottomLeftResizeHandle color: "gray" height: 5 width: 5 anchors.left: parent.left anchors.bottom: parent.bottom visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: westBound | southBound } } Rectangle { id: bottomRightResizeHandle color: "gray" height: 5 width: 5 anchors.right: parent.right anchors.bottom: parent.bottom visible: decorationsShowing HandleHandler { enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 flags: eastBound | southBound } } function constrainPoint(mousePos) { var x0 = usableArea.x var y0 = usableArea.y var x1 = x0 + usableArea.width var y1 = y0 + usableArea.height return Qt.point(Math.min(Math.max(x0,mousePos.x), x1), Math.min(Math.max(y0,mousePos.y), y1)) } function maxContentRect() { var x0 = usableArea.x + leftResizeHandle.width var x1 = usableArea.x + usableArea.width - rightResizeHandle.width var y0 = usableArea.y + topResizeHandle.height + titleBar.height var y1 = usableArea.y + usableArea.height - bottomResizeHandle.height return Qt.rect(x0, y0, x1 - x0, y1 - y0) } function randomPos(windowSize, screenSize) { var res = (windowSize >= screenSize) ? 0 : Math.floor(Math.random() * (screenSize - windowSize)) return res } function activate() { shellSurface.active = true shellSurfaceItem.raise() activated() } function deactivate() { shellSurface.active = true deactivated() } function setWindowState(nextState) { var currentState = chrome.windowState if (currentState === nextState) return console.log("setWindowState", nextState.toString(16)) if ((currentState & (Qt.WindowMinimized | Qt.WindowMaximized | Qt.WindowFullScreen)) == 0) chrome.oldGeometry = chrome.shellSurface.windowGeometry chrome.windowState = nextState if ((nextState & Qt.WindowMinimized) != 0) { console.log("MINIMIZE") chrome.shellSurface.requestWindowGeometry(nextState, Qt.rect(0, 0, 1, 1)) shellSurfaceItem.visible = false } else if ((nextState & Qt.WindowFullScreen) != 0) { console.log("FULLSCREENIZE") chrome.shellSurface.requestWindowGeometry(nextState, Qt.rect(0, 0, output.window.width, output.window.height)) shellSurfaceItem.visible = true } else if ((nextState & Qt.WindowMaximized) != 0) { console.log("MAXIMIZE") chrome.shellSurface.requestWindowGeometry(nextState, maxContentRect()) shellSurfaceItem.visible = true } else { console.log("NORMALIZE", chrome.oldGeometry) chrome.shellSurface.requestWindowGeometry(nextState, chrome.oldGeometry) shellSurfaceItem.visible = true } } Rectangle { id: titleBar anchors.top: topResizeHandle.bottom anchors.left: leftResizeHandle.right anchors.right: rightResizeHandle.left height: visible ? xButton.height + 10 : 0 color: shellSurface.active ? "cornflowerblue" : "lightgray" visible: !frameless Text { anchors.left: parent.left anchors.right: rowLayout.left anchors.verticalCenter: parent.verticalCenter font.pixelSize: xButton.height text: shellSurface.windowTitle fontSizeMode: Text.Fit } RowLayout { id: rowLayout anchors.right: parent.right anchors.rightMargin: 5 ToolButton { text: "-" Layout.margins: 5 visible: (chrome.windowFlags & Qt.WindowMinimizeButtonHint) != 0 onClicked: { var newState if ((shellSurface.windowState & Qt.WindowMinimized) != 0) newState = chrome.windowState & ~Qt.WindowMinimized else newState = chrome.windowState | Qt.WindowMinimized if ((newState & Qt.WindowMaximized) != 0) newState &= ~Qt.WindowMaximized setWindowState(newState) } } ToolButton { text: "+" Layout.margins: 5 visible: (chrome.windowFlags & Qt.WindowMaximizeButtonHint) != 0 onClicked: { var newState if ((shellSurface.windowState & Qt.WindowMaximized) != 0) newState = shellSurface.windowState & ~Qt.WindowMaximized else newState = shellSurface.windowState | Qt.WindowMaximized if ((newState & Qt.WindowMinimized) != 0) newState &= ~Qt.WindowMinimized setWindowState(newState) } } ToolButton { id: xButton text: "X" Layout.margins: 5 visible: (chrome.windowFlags & Qt.WindowCloseButtonHint) != 0 onClicked: shellSurface.sendClose() } } DragHandler { target: null property real xOffset: -1.0 property real yOffset: -1.0 property bool started: false enabled: (shellSurface.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 onGrabChanged: { started = false activate() } onCentroidChanged: { if (!active) return if (!started) { xOffset = shellSurface.windowPosition.x - centroid.scenePressPosition.x yOffset = shellSurface.windowPosition.y - centroid.scenePressPosition.y started = true chrome.positionAutomatic = false } var pos = chrome.constrainPoint(centroid.scenePosition) shellSurface.windowPosition = Qt.point(pos.x + xOffset, pos.y + yOffset) } } } ShellSurfaceItem { id: shellSurfaceItem anchors.top: titleBar.bottom anchors.bottom: bottomResizeHandle.top anchors.left: leftResizeHandle.right anchors.right: rightResizeHandle.left moveItem: chrome staysOnBottom: shellSurface.windowFlags & Qt.WindowStaysOnBottomHint staysOnTop: !staysOnBottom && shellSurface.windowFlags & Qt.WindowStaysOnTopHint function updateFrameMargins() { shellSurface.frameMarginLeft = (decorationsShowing ? leftResizeHandle.width : 0) shellSurface.frameMarginRight = (decorationsShowing ? rightResizeHandle.width : 0) shellSurface.frameMarginTop = (decorationsShowing ? topResizeHandle.height : 0) + (!frameless ? titleBar.height : 0) shellSurface.frameMarginBottom = (decorationsShowing ? bottomResizeHandle.height : 0) } Component.onCompleted: { updateFrameMargins() } onSurfaceDestroyed: { bufferLocked = true; destroyAnimation.start(); } Connections { target: shellSurface function onWindowFlagsChanged() { console.log("FLAGS", shellSurface.windowFlags.toString(16)) shellSurfaceItem.updateFrameMargins() } function onWindowStateChanged() { setWindowState(shellSurface.windowState) } function onActiveChanged() { if (shellSurface.active) { shellSurfaceItem.raise() activated() } else { deactivated() } } function onStartResize() { console.log("START SYSTEM RESIZE") } function onStartMove() { console.log("START SYSTEM MOVE") } function onRaiseRequested() { console.log("RAISE") shellSurfaceItem.raise() } function onLowerRequested() { console.log("LOWER") shellSurfaceItem.lower() } function onWindowGeometryChanged() { console.log("GEOM CHANGE", shellSurface.windowGeometry) } } Connections { target: shellSurface.surface function onHasContentChanged() { if (!chrome.positionSet) { var rect = shellSurface.windowGeometry var w = rect.width var h = rect.height var space = maxContentRect() var randomize = shellSurface.positionAutomatic var xpos = randomize ? randomPos(w, space.width) + space.x : Math.max(rect.x, space.x) var ypos = randomize ? randomPos(h, space.height) + space.y : Math.max(rect.y, space.y) shellSurface.windowPosition = Qt.point(xpos, ypos) } chrome.positionSet = true } } SequentialAnimation { id: destroyAnimation ParallelAnimation { NumberAnimation { target: scaleTransform; property: "yScale"; to: 2/height; duration: 150 } NumberAnimation { target: scaleTransform; property: "xScale"; to: 0.4; duration: 150 } NumberAnimation { target: chrome; property: "opacity"; to: chrome.isChild ? 0 : 1; duration: 150 } } NumberAnimation { target: scaleTransform; property: "xScale"; to: 0; duration: 150 } ScriptAction { script: chrome.destroyAnimationFinished() } } SequentialAnimation { id: receivedFocusAnimation ParallelAnimation { NumberAnimation { target: scaleTransform; property: "yScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } NumberAnimation { target: scaleTransform; property: "xScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } } ParallelAnimation { NumberAnimation { target: scaleTransform; property: "yScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } NumberAnimation { target: scaleTransform; property: "xScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } } } } }