Revision control

++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ import QtQuick
++ import QtQuick3D
++
++ Item {
++ id: root
++ required property Node origin
++ required property Camera camera
++
++ property real xSpeed: 0.1
++ property real ySpeed: 0.1
++
++ property bool xInvert: false
++ property bool yInvert: true
++
++ property bool mouseEnabled: true
++ property bool panEnabled: true
++ property bool automaticClipping: true
++
++ property alias acceptedButtons: dragHandler.acceptedButtons
++
++ readonly property bool inputsNeedProcessing: status.useMouse || status.isPanning
++
++ implicitWidth: parent.width
++ implicitHeight: parent.height
++
++ Connections {
++ enabled: root.automaticClipping
++ target: root.camera
++ function onZChanged() {
++ // Adjust near/far values based on distance
++ let distance = root.camera.z
++ if (distance < 1) {
++ root.camera.clipNear = 0.01
++ root.camera.clipFar = 100
++ if (root.camera.z === 0) {
++ console.warn("camera z set to 0, setting it to near clip")
++ root.camera.z = root.camera.clipNear
++ }
++ } else if (distance < 100) {
++ root.camera.clipNear = 0.1
++ root.camera.clipFar = 1000
++ } else {
++ root.camera.clipNear = 1
++ root.camera.clipFar = 10000
++ }
++ }
++ }
++
++ DragHandler {
++ id: dragHandler
++ target: null
++ enabled: root.mouseEnabled
++ acceptedModifiers: Qt.NoModifier
++ onCentroidChanged: {
++ root.mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false);
++ }
++
++ onActiveChanged: {
++ if (active)
++ root.mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y));
++ else
++ root.mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y));
++ }
++ }
++
++ DragHandler {
++ id: ctrlDragHandler
++ target: null
++ enabled: root.mouseEnabled && root.panEnabled
++ acceptedButtons: root.acceptedButtons
++ acceptedModifiers: Qt.ControlModifier
++ onCentroidChanged: {
++ root.panEvent(Qt.vector2d(centroid.position.x, centroid.position.y));
++ }
++
++ onActiveChanged: {
++ if (active)
++ root.startPan(Qt.vector2d(centroid.position.x, centroid.position.y));
++ else
++ root.endPan();
++ }
++ }
++
++ PinchHandler {
++ id: pinchHandler
++ target: null
++ enabled: root.mouseEnabled
++
++ onTranslationChanged: (delta) => {
++ if (!root.panEnabled)
++ return;
++ delta.x = -(delta.x / root.width) * root.camera.z;
++ delta.y = (delta.y / root.height) * root.camera.z;
++
++ let movement = Qt.vector3d(0, 0, 0)
++ // X Movement
++ let xDirection = root.origin.right
++ movement = movement.plus(Qt.vector3d(xDirection.x * delta.x,
++ xDirection.y * delta.x,
++ xDirection.z * delta.x));
++ // Y Movement
++ let yDirection = root.origin.up
++ movement = movement.plus(Qt.vector3d(yDirection.x * delta.y,
++ yDirection.y * delta.y,
++ yDirection.z * delta.y));
++
++ root.origin.position = root.origin.position.plus(movement)
++ }
++
++ onScaleChanged: (delta) => {
++ root.camera.z = root.camera.z * (1 / delta)
++ }
++ }
++
++ TapHandler {
++ acceptedButtons: root.acceptedButtons
++ onTapped: root.forceActiveFocus() // qmllint disable signal-handler-parameters
++ }
++
++ WheelHandler {
++ id: wheelHandler
++ orientation: Qt.Vertical
++ target: null
++ enabled: root.mouseEnabled
++ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
++ onWheel: event => {
++ let delta = -event.angleDelta.y * 0.01;
++ root.camera.z += root.camera.z * 0.1 * delta
++ }
++ }
++
++ function mousePressed(newPos) {
++ root.forceActiveFocus()
++ status.currentPos = newPos
++ status.lastPos = newPos
++ status.useMouse = true;
++ }
++
++ function mouseReleased(newPos) {
++ status.useMouse = false;
++ }
++
++ function mouseMoved(newPos: vector2d) {
++ status.currentPos = newPos;
++ }
++
++ function startPan(pos: vector2d) {
++ status.isPanning = true;
++ status.currentPanPos = pos;
++ status.lastPanPos = pos;
++ }
++
++ function endPan() {
++ status.isPanning = false;
++ }
++
++ function panEvent(newPos: vector2d) {
++ status.currentPanPos = newPos;
++ }
++
++ FrameAnimation {
++ id: updateTimer
++ running: root.inputsNeedProcessing
++ onTriggered: status.processInput(frameTime * 100)
++ }
++
++ QtObject {
++ id: status
++
++ property bool useMouse: false
++ property bool isPanning: false
++
++ property vector2d lastPos: Qt.vector2d(0, 0)
++ property vector2d lastPanPos: Qt.vector2d(0, 0)
++ property vector2d currentPos: Qt.vector2d(0, 0)
++ property vector2d currentPanPos: Qt.vector2d(0, 0)
++
++ function negate(vector) {
++ return Qt.vector3d(-vector.x, -vector.y, -vector.z)
++ }
++
++ function processInput(frameDelta) {
++ // To keep the sensitivty compatible with the old frame based system
++ // we add a constant frame delta factor equivalent to 60 fps
++ const legacyDeltaScale = 1.66666666667;
++ if (useMouse) {
++ // Get the delta
++ var rotationVector = root.origin.eulerRotation;
++ var delta = Qt.vector2d(lastPos.x - currentPos.x,
++ lastPos.y - currentPos.y);
++ // rotate x
++ var rotateX = delta.x * root.xSpeed * legacyDeltaScale
++ if (root.xInvert)
++ rotateX = -rotateX;
++ rotationVector.y += rotateX;
++
++ // rotate y
++ var rotateY = delta.y * -root.ySpeed * legacyDeltaScale
++ if (root.yInvert)
++ rotateY = -rotateY;
++ rotationVector.x += rotateY;
++ root.origin.setEulerRotation(rotationVector);
++ lastPos = currentPos;
++ }
++ if (isPanning) {
++ let delta = currentPanPos.minus(lastPanPos);
++ delta.x = -delta.x
++
++ delta.x = (delta.x / root.width) * root.camera.z * legacyDeltaScale
++ delta.y = (delta.y / root.height) * root.camera.z * legacyDeltaScale
++
++ let velocity = Qt.vector3d(0, 0, 0)
++ // X Movement
++ let xDirection = root.origin.right
++ velocity = velocity.plus(Qt.vector3d(xDirection.x * delta.x,
++ xDirection.y * delta.x,
++ xDirection.z * delta.x));
++ // Y Movement
++ let yDirection = root.origin.up
++ velocity = velocity.plus(Qt.vector3d(yDirection.x * delta.y,
++ yDirection.y * delta.y,
++ yDirection.z * delta.y));
++
++ root.origin.position = root.origin.position.plus(velocity)
++
++ lastPanPos = currentPanPos
++ }
++ }
++ }
++
++ }