Files
qt-creator/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml
Miikka Heikkinen 3890bf4efb QmlDesigner: Add support for multiple views to 3D edit view
Any subtree of Nodes can be shown in 3D edit view.
Selecting any Node or their children, or View3D changes the
view in 3D edit view to the relevant scene.

Fixes: QDS-1494
Fixes: QDS-1565
Change-Id: I2d5a6f88bab2a20b74c347351235f79fb530519b
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
2020-01-31 14:52:08 +00:00

621 lines
23 KiB
QML

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick3D 1.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
import MouseArea3D 1.0
Window {
id: viewWindow
width: 1024
height: 768
minimumHeight: 200
minimumWidth: 200
visible: true
title: "3D Edit View"
// need all those flags otherwise the title bar disappears after setting WindowStaysOnTopHint flag later
flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
property Node activeScene: null
property View3D editView: null
property alias showEditLight: btnEditViewLight.toggled
property alias usePerspective: btnPerspective.toggled
property alias globalOrientation: btnLocalGlobal.toggled
property Node selectedNode: null // This is non-null only in single selection case
property var selectedNodes: [] // All selected nodes
property var lightGizmos: []
property var cameraGizmos: []
property var selectionBoxes: []
property rect viewPortRect: Qt.rect(0, 0, 1000, 1000)
signal selectionChanged(var selectedNodes)
signal commitObjectProperty(var object, var propName)
signal changeObjectProperty(var object, var propName)
onUsePerspectiveChanged: _generalHelper.storeToolState("usePerspective", usePerspective)
onShowEditLightChanged: _generalHelper.storeToolState("showEditLight", showEditLight)
onGlobalOrientationChanged: _generalHelper.storeToolState("globalOrientation", globalOrientation)
onActiveSceneChanged: {
// importScene cannot be updated after initial set, so we need to reconstruct entire View3D
var component = Qt.createComponent("SceneView3D.qml");
if (component.status === Component.Ready) {
var oldView = editView;
if (editView)
editView.visible = false;
editView = component.createObject(viewRect,
{"usePerspective": usePerspective,
"showSceneLight": showEditLight,
"importScene": activeScene,
"z": 1});
editView.usePerspective = Qt.binding(function() {return usePerspective;});
editView.showSceneLight = Qt.binding(function() {return showEditLight;});
selectionBoxes.length = 0;
ensureSelectionBoxes(1);
if (oldView)
oldView.destroy();
}
}
function updateToolStates(toolStates) {
// Init the stored state so we don't unnecessarily reflect changes back to creator
_generalHelper.initToolStates(toolStates);
_generalHelper.restoreWindowState(viewWindow, toolStates);
if ("showEditLight" in toolStates)
showEditLight = toolStates.showEditLight;
if ("usePerspective" in toolStates)
usePerspective = toolStates.usePerspective;
if ("globalOrientation" in toolStates)
globalOrientation = toolStates.globalOrientation;
var groupIndex;
var group;
var i;
if ("groupSelect" in toolStates) {
groupIndex = toolStates.groupSelect;
group = toolbarButtons.buttonGroups["groupSelect"];
for (i = 0; i < group.length; ++i)
group[i].selected = (i === groupIndex);
}
if ("groupTransform" in toolStates) {
groupIndex = toolStates.groupTransform;
group = toolbarButtons.buttonGroups["groupTransform"];
for (i = 0; i < group.length; ++i)
group[i].selected = (i === groupIndex);
}
if ("editCamState" in toolStates)
cameraControl.restoreCameraState(toolStates.editCamState);
}
function ensureSelectionBoxes(count) {
var needMore = count - selectionBoxes.length
if (needMore > 0) {
var component = Qt.createComponent("SelectionBox.qml");
if (component.status === Component.Ready) {
for (var i = 0; i < needMore; ++i) {
var geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry");
var boxParent = null;
if (editView)
boxParent = editView.sceneHelpers;
var box = component.createObject(boxParent, {"view3D": editView,
"geometryName": geometryName});
selectionBoxes[selectionBoxes.length] = box;
box.view3D = Qt.binding(function() {return editView;});
}
}
}
}
function selectObjects(objects) {
// Create selection boxes as necessary. One more box than is actually needed is created, so
// that we always have a previously created box to use for new selection.
// This fixes an occasional visual glitch when creating a new box.
ensureSelectionBoxes(objects.length + 1)
var i;
for (i = 0; i < objects.length; ++i)
selectionBoxes[i].targetNode = objects[i];
for (i = objects.length; i < selectionBoxes.length; ++i)
selectionBoxes[i].targetNode = null;
selectedNodes = objects;
if (objects.length === 0 || objects.length > 1)
selectedNode = null;
else
selectedNode = objects[0];
}
function handleObjectClicked(object, multi) {
var theObject = object;
if (btnSelectGroup.selected) {
while (theObject && theObject.parent !== activeScene)
theObject = theObject.parent;
}
// Object selection logic:
// Regular click: Clear any multiselection, single-selects the clicked object
// Ctrl-click: No objects selected: Act as single select
// One or more objects selected: Multiselect
// Null object always clears entire selection
var newSelection = [];
if (object !== null) {
if (multi && selectedNodes.length > 0) {
var deselect = false;
for (var i = 0; i < selectedNodes.length; ++i) {
// Multiselecting already selected object clears that object from selection
if (selectedNodes[i] !== object)
newSelection[newSelection.length] = selectedNodes[i];
else
deselect = true;
}
if (!deselect)
newSelection[newSelection.length] = object;
} else {
newSelection[0] = theObject;
}
}
selectObjects(newSelection);
selectionChanged(newSelection);
}
function addLightGizmo(scene, obj)
{
// Insert into first available gizmo
for (var i = 0; i < lightGizmos.length; ++i) {
if (!lightGizmos[i].targetNode) {
lightGizmos[i].scene = scene;
lightGizmos[i].targetNode = obj;
return;
}
}
// No free gizmos available, create a new one
var component = Qt.createComponent("LightGizmo.qml");
if (component.status === Component.Ready) {
var gizmo = component.createObject(overlayScene,
{"view3D": overlayView, "targetNode": obj,
"selectedNodes": selectedNodes, "scene": scene,
"activeScene": activeScene});
lightGizmos[lightGizmos.length] = gizmo;
gizmo.clicked.connect(handleObjectClicked);
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
gizmo.activeScene = Qt.binding(function() {return activeScene;});
}
}
function addCameraGizmo(scene, obj)
{
// Insert into first available gizmo
for (var i = 0; i < cameraGizmos.length; ++i) {
if (!cameraGizmos[i].targetNode) {
cameraGizmos[i].scene = scene;
cameraGizmos[i].targetNode = obj;
return;
}
}
// No free gizmos available, create a new one
var component = Qt.createComponent("CameraGizmo.qml");
if (component.status === Component.Ready) {
var geometryName = _generalHelper.generateUniqueName("CameraGeometry");
var gizmo = component.createObject(
overlayScene,
{"view3D": overlayView, "targetNode": obj, "geometryName": geometryName,
"viewPortRect": viewPortRect, "selectedNodes": selectedNodes,
"scene": scene, "activeScene": activeScene});
cameraGizmos[cameraGizmos.length] = gizmo;
gizmo.clicked.connect(handleObjectClicked);
gizmo.viewPortRect = Qt.binding(function() {return viewPortRect;});
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
gizmo.activeScene = Qt.binding(function() {return activeScene;});
}
}
// Work-around the fact that the projection matrix for the camera is not calculated until
// the first frame is rendered, so any initial calls to mapFrom3DScene() will fail.
Component.onCompleted: {
selectObjects([]);
_generalHelper.requestOverlayUpdate();
}
onWidthChanged: {
_generalHelper.requestOverlayUpdate();
_generalHelper.storeWindowState(viewWindow);
}
onHeightChanged: {
_generalHelper.requestOverlayUpdate();
_generalHelper.storeWindowState(viewWindow);
}
onXChanged: _generalHelper.storeWindowState(viewWindow);
onYChanged: _generalHelper.storeWindowState(viewWindow);
onWindowStateChanged: _generalHelper.storeWindowState(viewWindow);
Node {
id: overlayScene
PerspectiveCamera {
id: overlayPerspectiveCamera
clipFar: viewWindow.editView ? viewWindow.editView.perpectiveCamera.clipFar : 1000
clipNear: viewWindow.editView ? viewWindow.editView.perpectiveCamera.clipNear : 1
position: viewWindow.editView ? viewWindow.editView.perpectiveCamera.position : Qt.vector3d(0, 0, 0)
rotation: viewWindow.editView ? viewWindow.editView.perpectiveCamera.rotation : Qt.vector3d(0, 0, 0)
}
OrthographicCamera {
id: overlayOrthoCamera
clipFar: viewWindow.editView ? viewWindow.editView.orthoCamera.clipFar : 1000
clipNear: viewWindow.editView ? viewWindow.editView.orthoCamera.clipNear : 1
position: viewWindow.editView ? viewWindow.editView.orthoCamera.position : Qt.vector3d(0, 0, 0)
rotation: viewWindow.editView ? viewWindow.editView.orthoCamera.rotation : Qt.vector3d(0, 0, 0)
scale: viewWindow.editView ? viewWindow.editView.orthoCamera.scale : Qt.vector3d(0, 0, 0)
}
MouseArea3D {
id: gizmoDragHelper
view3D: overlayView
}
MoveGizmo {
id: moveGizmo
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
highlightOnHover: true
targetNode: viewWindow.selectedNode
globalOrientation: viewWindow.globalOrientation
visible: viewWindow.selectedNode && btnMove.selected
view3D: overlayView
dragHelper: gizmoDragHelper
onPositionCommit: viewWindow.commitObjectProperty(viewWindow.selectedNode, "position")
onPositionMove: viewWindow.changeObjectProperty(viewWindow.selectedNode, "position")
}
ScaleGizmo {
id: scaleGizmo
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
highlightOnHover: true
targetNode: viewWindow.selectedNode
visible: viewWindow.selectedNode && btnScale.selected
view3D: overlayView
dragHelper: gizmoDragHelper
onScaleCommit: viewWindow.commitObjectProperty(viewWindow.selectedNode, "scale")
onScaleChange: viewWindow.changeObjectProperty(viewWindow.selectedNode, "scale")
}
RotateGizmo {
id: rotateGizmo
scale: autoScale.getScale(Qt.vector3d(7, 7, 7))
highlightOnHover: true
targetNode: viewWindow.selectedNode
globalOrientation: viewWindow.globalOrientation
visible: viewWindow.selectedNode && btnRotate.selected
view3D: overlayView
dragHelper: gizmoDragHelper
onRotateCommit: viewWindow.commitObjectProperty(viewWindow.selectedNode, "rotation")
onRotateChange: viewWindow.changeObjectProperty(viewWindow.selectedNode, "rotation")
}
AutoScaleHelper {
id: autoScale
view3D: overlayView
position: moveGizmo.scenePosition
orientation: moveGizmo.orientation
}
Line3D {
id: pivotLine
visible: viewWindow.selectedNode
name: "3D Edit View Pivot Line"
color: "#ddd600"
function flipIfNeeded(vec) {
if (!viewWindow.selectedNode || viewWindow.selectedNode.orientation === Node.LeftHanded)
return vec;
else
return Qt.vector3d(vec.x, vec.y, -vec.z);
}
startPos: viewWindow.selectedNode ? flipIfNeeded(viewWindow.selectedNode.scenePosition)
: Qt.vector3d(0, 0, 0)
Connections {
target: viewWindow
onSelectedNodeChanged: {
pivotLine.endPos = pivotLine.flipIfNeeded(gizmoDragHelper.pivotScenePosition(
viewWindow.selectedNode));
}
}
Connections {
target: viewWindow.selectedNode
onSceneTransformChanged: {
pivotLine.endPos = pivotLine.flipIfNeeded(gizmoDragHelper.pivotScenePosition(
viewWindow.selectedNode));
}
}
Model {
id: pivotCap
source: "#Sphere"
scale: autoScale.getScale(Qt.vector3d(0.03, 0.03, 0.03))
position: pivotLine.startPos
materials: [
DefaultMaterial {
id: lineMat
lighting: DefaultMaterial.NoLighting
cullingMode: Material.DisableCulling
emissiveColor: pivotLine.color
}
]
}
}
}
Rectangle {
id: viewRect
anchors.fill: parent
focus: true
gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" }
GradientStop { position: 0.0; color: "#999999" }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (viewWindow.editView) {
var pickResult = viewWindow.editView.pick(mouse.x, mouse.y);
handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit),
mouse.modifiers & Qt.ControlModifier);
if (!pickResult.objectHit)
mouse.accepted = false;
}
}
}
DropArea {
anchors.fill: parent
}
View3D {
id: overlayView
anchors.fill: parent
camera: usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera
importScene: overlayScene
z: 2
}
Overlay2D {
id: gizmoLabel
targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo
targetView: overlayView
visible: targetNode.dragging
z: 3
Rectangle {
color: "white"
x: -width / 2
y: -height - 8
width: gizmoLabelText.width + 4
height: gizmoLabelText.height + 4
border.width: 1
Text {
id: gizmoLabelText
text: {
var l = Qt.locale();
var targetProperty;
if (viewWindow.selectedNode) {
if (gizmoLabel.targetNode === moveGizmo)
targetProperty = viewWindow.selectedNode.position;
else
targetProperty = viewWindow.selectedNode.scale;
return qsTr("x:") + Number(targetProperty.x).toLocaleString(l, 'f', 1)
+ qsTr(" y:") + Number(targetProperty.y).toLocaleString(l, 'f', 1)
+ qsTr(" z:") + Number(targetProperty.z).toLocaleString(l, 'f', 1);
} else {
return "";
}
}
anchors.centerIn: parent
}
}
}
EditCameraController {
id: cameraControl
camera: viewWindow.editView ? viewWindow.editView.camera : null
anchors.fill: parent
view3d: viewWindow.editView
}
}
Rectangle { // toolbar
id: toolbar
color: "#9F000000"
width: 35
height: toolbarButtons.height
Column {
id: toolbarButtons
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
padding: 5
// Button groups must be defined in parent object of buttons
property var buttonGroups: {
"groupSelect": [btnSelectGroup, btnSelectItem],
"groupTransform": [btnMove, btnRotate, btnScale]
}
ToolBarButton {
id: btnSelectItem
selected: true
tooltip: qsTr("Select Item")
shortcut: "Q"
currentShortcut: selected ? "" : shortcut
tool: "item_selection"
buttonGroup: "groupSelect"
}
ToolBarButton {
id: btnSelectGroup
tooltip: qsTr("Select Group")
shortcut: "Q"
currentShortcut: btnSelectItem.currentShortcut === shortcut ? "" : shortcut
tool: "group_selection"
buttonGroup: "groupSelect"
}
Rectangle { // separator
width: 25
height: 1
color: "#f1f1f1"
anchors.horizontalCenter: parent.horizontalCenter
}
ToolBarButton {
id: btnMove
selected: true
tooltip: qsTr("Move current selection")
shortcut: "W"
currentShortcut: shortcut
tool: "move"
buttonGroup: "groupTransform"
}
ToolBarButton {
id: btnRotate
tooltip: qsTr("Rotate current selection")
shortcut: "E"
currentShortcut: shortcut
tool: "rotate"
buttonGroup: "groupTransform"
}
ToolBarButton {
id: btnScale
tooltip: qsTr("Scale current selection")
shortcut: "R"
currentShortcut: shortcut
tool: "scale"
buttonGroup: "groupTransform"
}
Rectangle { // separator
width: 25
height: 1
color: "#f1f1f1"
anchors.horizontalCenter: parent.horizontalCenter
}
ToolBarButton {
id: btnFit
tooltip: qsTr("Fit camera to current selection")
shortcut: "F"
currentShortcut: shortcut
tool: "fit"
togglable: false
onSelectedChanged: {
if (viewWindow.editView && selected) {
var targetNode = viewWindow.selectedNodes.length > 0
? selectionBoxes[0].model : null;
cameraControl.focusObject(targetNode, viewWindow.editView.camera.rotation, true);
}
}
}
}
}
AxisHelper {
anchors.right: parent.right
anchors.top: parent.top
width: 100
height: width
editCameraCtrl: cameraControl
selectedNode : viewWindow.selectedNodes.length ? selectionBoxes[0].model : null
}
Rectangle { // top controls bar
color: "#aa000000"
width: 290
height: btnPerspective.height + 10
anchors.top: parent.top
anchors.right: parent.right
anchors.rightMargin: 100
Row {
padding: 5
anchors.fill: parent
ToggleButton {
id: btnPerspective
width: 105
tooltip: qsTr("Toggle Perspective / Orthographic Projection")
states: [{iconId: "ortho", text: qsTr("Orthographic")}, {iconId: "persp", text: qsTr("Perspective")}]
}
ToggleButton {
id: btnLocalGlobal
width: 65
tooltip: qsTr("Toggle Global / Local Orientation")
states: [{iconId: "local", text: qsTr("Local")}, {iconId: "global", text: qsTr("Global")}]
}
ToggleButton {
id: btnEditViewLight
width: 110
toggleBackground: true
tooltip: qsTr("Toggle Edit Light")
states: [{iconId: "edit_light_off", text: qsTr("Edit Light Off")}, {iconId: "edit_light_on", text: qsTr("Edit Light On")}]
}
}
}
Text {
id: helpText
property string modKey: _generalHelper.isMacOS ? qsTr("Option") : qsTr("Alt")
color: "white"
text: qsTr("Camera controls: ") + modKey
+ qsTr(" + mouse press and drag. Left: Rotate, Middle: Pan, Right/Wheel: Zoom.")
anchors.bottom: parent.bottom
}
}