forked from qt-creator/qt-creator
Tool state of the scene is linked to qml id of the scene root, so that tool states are preserved through delete and undo, which causes generation of new internal ids for instances. Tool state storage doesn't currently survive changing of scene root qml id, as tracking that isn't trivial. It's not enough to simply track id change commands because model repurposes existing nodes when major graph changes occur, causing unexpected id changes e.g. when delete/undo is done. Change-Id: I74d11ce47e86ce5d29796399c4a91b65980b1597 Fixes: QDS-1536 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
700 lines
26 KiB
QML
700 lines
26 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: qsTr("3D Edit View [") + sceneId + qsTr("]")
|
|
// 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 string sceneId
|
|
|
|
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(sceneId, "usePerspective", usePerspective)
|
|
onShowEditLightChanged: _generalHelper.storeToolState(sceneId,"showEditLight", showEditLight)
|
|
onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation)
|
|
|
|
onActiveSceneChanged: {
|
|
if (editView) {
|
|
// Destroy is async, so make sure we don't get any more updates for the old editView
|
|
_generalHelper.enableItemUpdate(editView, false);
|
|
editView.destroy();
|
|
}
|
|
if (activeScene) {
|
|
// 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) {
|
|
editView = component.createObject(viewRect,
|
|
{"usePerspective": usePerspective,
|
|
"showSceneLight": showEditLight,
|
|
"importScene": activeScene,
|
|
"cameraZoomFactor": cameraControl._zoomFactor,
|
|
"z": 1});
|
|
editView.usePerspective = Qt.binding(function() {return usePerspective;});
|
|
editView.showSceneLight = Qt.binding(function() {return showEditLight;});
|
|
editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;});
|
|
|
|
selectionBoxes.length = 0;
|
|
updateToolStates();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Disables edit view update if scene doesn't match current activeScene.
|
|
// If it matches, updates are enabled.
|
|
function enableEditViewUpdate(scene)
|
|
{
|
|
if (editView)
|
|
_generalHelper.enableItemUpdate(editView, (scene && scene === activeScene));
|
|
}
|
|
|
|
|
|
function restoreWindowState()
|
|
{
|
|
// It is expected that tool states have been initialized before calling this
|
|
_generalHelper.restoreWindowState(viewWindow);
|
|
}
|
|
|
|
function updateToolStates()
|
|
{
|
|
var toolStates = _generalHelper.getToolStates(sceneId);
|
|
if ("showEditLight" in toolStates)
|
|
showEditLight = toolStates.showEditLight;
|
|
else
|
|
showEditLight = false;
|
|
if ("usePerspective" in toolStates)
|
|
usePerspective = toolStates.usePerspective;
|
|
else
|
|
usePerspective = false;
|
|
if ("globalOrientation" in toolStates)
|
|
globalOrientation = toolStates.globalOrientation;
|
|
else
|
|
globalOrientation = false;
|
|
|
|
var groupIndex;
|
|
var group;
|
|
var i;
|
|
btnSelectItem.selected = false;
|
|
btnSelectGroup.selected = true;
|
|
if ("groupSelect" in toolStates) {
|
|
groupIndex = toolStates.groupSelect;
|
|
group = toolbarButtons.buttonGroups["groupSelect"];
|
|
for (i = 0; i < group.length; ++i)
|
|
group[i].selected = (i === groupIndex);
|
|
}
|
|
|
|
btnRotate.selected = false;
|
|
btnScale.selected = false;
|
|
btnMove.selected = true;
|
|
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);
|
|
else
|
|
cameraControl.restoreDefaultState();
|
|
}
|
|
|
|
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 !== activeScene && 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;});
|
|
}
|
|
}
|
|
|
|
function releaseLightGizmo(obj)
|
|
{
|
|
for (var i = 0; i < lightGizmos.length; ++i) {
|
|
if (lightGizmos[i].targetNode === obj) {
|
|
lightGizmos[i].scene = null;
|
|
lightGizmos[i].targetNode = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function releaseCameraGizmo(obj)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === obj) {
|
|
cameraGizmos[i].scene = null;
|
|
cameraGizmos[i].targetNode = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateLightGizmoScene(scene, obj)
|
|
{
|
|
for (var i = 0; i < lightGizmos.length; ++i) {
|
|
if (lightGizmos[i].targetNode === obj) {
|
|
lightGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateCameraGizmoScene(scene, obj)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === obj) {
|
|
cameraGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
}
|
|
|
|
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"
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
|
|
ToolBarButton {
|
|
id: btnSelectGroup
|
|
tooltip: qsTr("Select Group")
|
|
shortcut: "Q"
|
|
currentShortcut: btnSelectItem.currentShortcut === shortcut ? "" : shortcut
|
|
tool: "group_selection"
|
|
buttonGroup: "groupSelect"
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
|
|
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"
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
|
|
ToolBarButton {
|
|
id: btnRotate
|
|
tooltip: qsTr("Rotate current selection")
|
|
shortcut: "E"
|
|
currentShortcut: shortcut
|
|
tool: "rotate"
|
|
buttonGroup: "groupTransform"
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
|
|
ToolBarButton {
|
|
id: btnScale
|
|
tooltip: qsTr("Scale current selection")
|
|
shortcut: "R"
|
|
currentShortcut: shortcut
|
|
tool: "scale"
|
|
buttonGroup: "groupTransform"
|
|
sceneId: viewWindow.sceneId
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|