diff options
Diffstat (limited to 'tests/manual/qt-shell/qml')
-rw-r--r-- | tests/manual/qt-shell/qml/Chrome.qml | 458 | ||||
-rw-r--r-- | tests/manual/qt-shell/qml/CompositorScreen.qml | 144 | ||||
-rw-r--r-- | tests/manual/qt-shell/qml/HandleHandler.qml | 86 | ||||
-rw-r--r-- | tests/manual/qt-shell/qml/Keyboard.qml | 12 | ||||
-rw-r--r-- | tests/manual/qt-shell/qml/main.qml | 22 |
5 files changed, 722 insertions, 0 deletions
diff --git a/tests/manual/qt-shell/qml/Chrome.qml b/tests/manual/qt-shell/qml/Chrome.qml new file mode 100644 index 000000000..9eb2f1965 --- /dev/null +++ b/tests/manual/qt-shell/qml/Chrome.qml @@ -0,0 +1,458 @@ +// 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 } + } + } + } +} diff --git a/tests/manual/qt-shell/qml/CompositorScreen.qml b/tests/manual/qt-shell/qml/CompositorScreen.qml new file mode 100644 index 000000000..92b75b348 --- /dev/null +++ b/tests/manual/qt-shell/qml/CompositorScreen.qml @@ -0,0 +1,144 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtWayland.Compositor + +WaylandOutput { + id: output + + property ListModel shellSurfaces: ListModel {} + property bool isNestedCompositor: Qt.platform.pluginName.startsWith("wayland") || Qt.platform.pluginName === "xcb" + property int currentActiveWindow: -1 + + function handleShellSurface(shellSurface) { + shellSurfaces.append({shellSurface: shellSurface}); + } + + // During development, it can be useful to start the compositor inside X11 or + // another Wayland compositor. In such cases, set sizeFollowsWindow to true to + // enable resizing of the compositor window to be forwarded to the Wayland clients + // as the output (screen) changing resolution. Consider setting it to false if you + // are running the compositor using eglfs, linuxfb or similar QPA backends. + sizeFollowsWindow: output.isNestedCompositor + + window: Window { + width: 1920 + height: 1080 + visible: true + + WaylandMouseTracker { + id: mouseTracker + + anchors.fill: parent + + // Set this to false to disable the outer mouse cursor when running nested + // compositors. Otherwise you would see two mouse cursors, one for each compositor. + windowSystemCursorEnabled: output.isNestedCompositor + + Image { + id: background + + anchors.fill: parent + fillMode: Image.Tile + source: "qrc:/images/background.jpg" + smooth: true + + Repeater { + id: chromeRepeater + model: output.shellSurfaces + // Chrome displays a shell surface on the screen (See Chrome.qml) + Chrome { + shellSurface: modelData + onDestroyAnimationFinished: + { + if (currentActiveWindow > index) { + --currentActiveWindow + } else if (currentActiveWindow === index) { + currentActiveWindow = index - 1 + if (currentActiveWindow >= 0) { + var nextActiveSurface = output.shellSurfaces.get(currentActiveWindow).shellSurface + if (nextActiveSurface !== undefined) // More than one surface can get destroyed at the same time + nextActiveSurface.active = true + } + } + output.shellSurfaces.remove(index) + } + + onDeactivated: { + if (index === currentActiveWindow) + currentActiveWindow = -1 + } + + onActivated: { + if (index !== currentActiveWindow && currentActiveWindow >= 0) { + // This may already have been destroyed + if (output.shellSurfaces.get(currentActiveWindow).shellSurface !== undefined) + output.shellSurfaces.get(currentActiveWindow).shellSurface.active = false + } + + currentActiveWindow = index + } + } + } + } + + Rectangle { + anchors.fill: taskbar + color: "lavenderblush" + } + + Row { + id: taskbar + height: 40 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + Repeater { + anchors.fill: parent + model: output.shellSurfaces + + ToolButton { + anchors.verticalCenter: parent.verticalCenter + text: modelData.windowTitle + onClicked: { + modelData.requestWindowGeometry(modelData.windowState & ~Qt.WindowMinimized, + modelData.windowGeometry) + chromeRepeater.itemAt(index).activate() + } + } + } + } + + Item { + id: usableArea + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: taskbar.top + } + + // Virtual Keyboard + Loader { + anchors.fill: parent + source: "Keyboard.qml" + } + + // Draws the mouse cursor for a given Wayland seat + WaylandCursorItem { + inputEventsEnabled: false + x: mouseTracker.mouseX + y: mouseTracker.mouseY + seat: output.compositor.defaultSeat + } + } + + Shortcut { + sequence: "Ctrl+Alt+Backspace" + onActivated: Qt.quit() + } + } +} diff --git a/tests/manual/qt-shell/qml/HandleHandler.qml b/tests/manual/qt-shell/qml/HandleHandler.qml new file mode 100644 index 000000000..7b31ad4d5 --- /dev/null +++ b/tests/manual/qt-shell/qml/HandleHandler.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +DragHandler { + target: null + + function selectCursor(f) + { + switch (f) { + case (southBound | eastBound): + case (northBound | westBound): + return Qt.SizeFDiagCursor + case (southBound | westBound): + case (northBound | eastBound): + return Qt.SizeBDiagCursor + case westBound: + case eastBound: + return Qt.SizeHorCursor + default: + return Qt.SizeVerCursor + } + } + + property int flags: WestBound + readonly property int westBound: 1 + readonly property int eastBound: 2 + readonly property int northBound: 4 + readonly property int southBound: 8 + + cursorShape: selectCursor(flags) + + property rect geom + property real startX: -1.0 + property real startY: -1.0 + property bool started: false + onGrabChanged: { + started = false + } + onCentroidChanged: { + if (!active) + return + if (!started) { + geom = shellSurface.windowGeometry + startX = centroid.scenePressPosition.x + startY = centroid.scenePressPosition.y + started = true + } + + var pos = chrome.constrainPoint(centroid.scenePosition) + var dx = pos.x - startX + var dy = pos.y - startY + + var minWidth = Math.max(0, shellSurface.minimumSize.width) + var minHeight = Math.max(0, shellSurface.minimumSize.height) + + var maxWidth = shellSurface.maximumSize.width > 0 ? shellSurface.maximumSize.width : Number.MAX_VALUE + var maxHeight = shellSurface.maximumSize.height > 0 ? shellSurface.maximumSize.height : Number.MAX_VALUE + + var newLeft = geom.left + if (flags & westBound) + newLeft = Math.max(geom.right - maxWidth, Math.min(geom.right - minWidth, newLeft + dx)); + + var newTop = geom.top + if (flags & northBound) + newTop = Math.max(geom.bottom - maxHeight, Math.min(geom.bottom - minHeight, newTop + dy)); + + var newRight = geom.right + if (flags & eastBound) + newRight = Math.max(geom.left, newRight + dx); + + var newBottom = geom.bottom + if (flags & southBound) + newBottom = Math.max(geom.top, newBottom + dy); + + console.log("RESIZE HANDLER", shellSurface.windowGeometry, geom, dy, newTop) + + + shellSurface.requestWindowGeometry(shellSurface.windowState, + Qt.rect(newLeft, + newTop, + newRight - newLeft, + newBottom - newTop)) + } +} diff --git a/tests/manual/qt-shell/qml/Keyboard.qml b/tests/manual/qt-shell/qml/Keyboard.qml new file mode 100644 index 000000000..a6e1bec30 --- /dev/null +++ b/tests/manual/qt-shell/qml/Keyboard.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.VirtualKeyboard + +InputPanel { + visible: active + y: active ? parent.height - height : parent.height + anchors.left: parent.left + anchors.right: parent.right +} diff --git a/tests/manual/qt-shell/qml/main.qml b/tests/manual/qt-shell/qml/main.qml new file mode 100644 index 000000000..986e0a490 --- /dev/null +++ b/tests/manual/qt-shell/qml/main.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtWayland.Compositor +import QtWayland.Compositor.QtShell + +WaylandCompositor { + id: waylandCompositor + + CompositorScreen { id: screen; compositor: waylandCompositor } + + // Shell surface extension. Needed to provide a window concept for Wayland clients. + // I.e. requests and events for maximization, minimization, resizing, closing etc. + + QtShell { + onQtShellSurfaceCreated: screen.handleShellSurface(qtShellSurface) + } + + // Extension for Input Method (QT_IM_MODULE) support at compositor-side + QtTextInputMethodManager {} +} |