diff --git a/include/ignition/gui/qml/GzPose.qml b/include/ignition/gui/qml/GzPose.qml
new file mode 100644
index 000000000..3cc05efa3
--- /dev/null
+++ b/include/ignition/gui/qml/GzPose.qml
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2022 Open Source Robotics Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.3
+import QtQuick.Controls.Styles 1.4
+
+/**
+ * Item displaying 3D pose information.
+ *
+ * Users should load values to xValues, yValues, etc.
+ * If readOnly == False,
+ * users can read from signal pararmeters of gzPoseSet: _x, _y, etc.
+ *
+ * Usage example:
+ * GzPose {
+ * id: gzPose
+ * readOnly: false
+ * xValue: xValueFromCPP
+ * yValue: yValueFromCPP
+ * zValue: zValueFromCPP
+ * rollValue: rollValueFromCPP
+ * pitchValue: pitchValueFromCPP
+ * yawValue: yawValueFromCPP
+ * onGzPoseSet: {
+ * myFunc(_x, _y, _z, _roll, _pitch, _yaw)
+ * }
+ * }
+**/
+
+Item {
+ id: gzPoseRoot
+
+ // Read-only / write
+ property bool readOnly: false
+
+ // User input value.
+ property double xValue
+ property double yValue
+ property double zValue
+ property double rollValue
+ property double pitchValue
+ property double yawValue
+
+ /**
+ * Used to read spinbox values
+ * @params: _x, _y, _z, _roll, _pitch, _yaw: corresponding spinBoxes values
+ * @note: When readOnly == false, user should read spinbox value from its
+ * parameters.
+ * When readOnly == true, this signal is unused.
+ */
+ signal gzPoseSet(double _x, double _y, double _z, double _roll, double _pitch, double _yaw)
+
+
+ /*** The following are private variables: ***/
+ // Show Pose bar (used to control expand)
+ property bool show: true
+
+ height: gzPoseContent.height
+
+ // Left indentation
+ property int indentation: 10
+
+ // Horizontal margins
+ property int margin: 5
+
+ // Maximum spinbox value
+ property double spinMax: 1000000
+
+ // local variables to store spinbox values
+ property var xItem: {}
+ property var yItem: {}
+ property var zItem: {}
+ property var rollItem: {}
+ property var pitchItem: {}
+ property var yawItem: {}
+
+ // Dummy component to use its functions.
+ IgnHelpers {
+ id: gzHelper
+ }
+ /*** Private variables end: ***/
+
+ /**
+ * Used to create a spin box
+ */
+ Component {
+ id: writableNumber
+ IgnSpinBox {
+ id: writableSpin
+ value: numberValue
+ minimumValue: -spinMax
+ maximumValue: spinMax
+ decimals: gzHelper.getDecimals(writableSpin.width)
+ onEditingFinished: {
+ gzPoseRoot.gzPoseSet(xItem.value, yItem.value, zItem.value, rollItem.value, pitchItem.value, yawItem.value)
+ }
+ }
+ }
+
+ /**
+ * Used to create a read-only number
+ */
+ Component {
+ id: readOnlyNumber
+ Text {
+ id: numberText
+ anchors.fill: parent
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ text: {
+ var decimals = gzHelper.getDecimals(numberText.width)
+ return numberValue.toFixed(decimals)
+ }
+ }
+ }
+
+ Rectangle {
+ id: gzPoseContent
+ width: parent.width
+ height: show ? gzPoseGrid.height : 0
+ clip: true
+ color: "transparent"
+
+ Behavior on height {
+ NumberAnimation {
+ duration: 200;
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ GridLayout {
+ id: gzPoseGrid
+ width: parent.width
+ columns: 6
+
+ // Left spacer
+ Item {
+ Layout.rowSpan: 3
+ width: margin + indentation
+ }
+
+ Text {
+ text: 'X (m)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: xLoader
+ anchors.fill: parent
+ property double numberValue: xValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ xItem = xLoader.item
+ }
+ }
+ }
+
+ Text {
+ text: 'Roll (rad)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: rollLoader
+ anchors.fill: parent
+ property double numberValue: rollValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ rollItem = rollLoader.item
+ }
+ }
+ }
+
+ // Right spacer
+ Item {
+ Layout.rowSpan: 3
+ width: margin
+ }
+
+ Text {
+ text: 'Y (m)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: yLoader
+ anchors.fill: parent
+ property double numberValue: yValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ yItem = yLoader.item
+ }
+ }
+ }
+
+ Text {
+ text: 'Pitch (rad)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: pitchLoader
+ anchors.fill: parent
+ property double numberValue: pitchValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ pitchItem = pitchLoader.item
+ }
+ }
+ }
+
+ Text {
+ text: 'Z (m)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: zLoader
+ anchors.fill: parent
+ property double numberValue: zValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ zItem = zLoader.item
+ }
+ }
+ }
+
+ Text {
+ text: 'Yaw (rad)'
+ leftPadding: 5
+ color: Material.theme == Material.Light ? "#444444" : "#bbbbbb"
+ font.pointSize: 12
+ }
+
+ Item {
+ Layout.fillWidth: true
+ height: 40
+ Loader {
+ id: yawLoader
+ anchors.fill: parent
+ property double numberValue: yawValue
+ sourceComponent: readOnly ? readOnlyNumber : writableNumber
+ onLoaded: {
+ yawItem = yawLoader.item
+ }
+ }
+ }
+ } // end of GridLayout
+ } // end of Rectangle (gzPoseContent)
+} // end of Rectangle (gzPoseRoot)
diff --git a/include/ignition/gui/qml/IgnHelpers.qml b/include/ignition/gui/qml/IgnHelpers.qml
index 57c0619b3..9938872f8 100644
--- a/include/ignition/gui/qml/IgnHelpers.qml
+++ b/include/ignition/gui/qml/IgnHelpers.qml
@@ -39,4 +39,21 @@ Item {
return result;
}
+
+ /**
+ * Helper function to get number of decimal digits based on a width value.
+ * @param _width Pixel width.
+ * @returns Number of decimals that fit with the provided width.
+ */
+ function getDecimals(_width) {
+ // Use full decimals if the width is <= 0, which allows the value
+ // to appear correctly.
+ if (_width <= 0 || _width > 110)
+ return 6
+
+ if (_width <= 80)
+ return 2
+
+ return 4
+ }
}
diff --git a/include/ignition/gui/resources.qrc b/include/ignition/gui/resources.qrc
index df7f2ced7..2a01f294a 100644
--- a/include/ignition/gui/resources.qrc
+++ b/include/ignition/gui/resources.qrc
@@ -3,6 +3,7 @@
qtquickcontrols2.conf
qml/GzColor.qml
+ qml/GzPose.qml
qml/IgnCard.qml
qml/IgnCardSettings.qml
qml/IgnHelpers.qml
@@ -27,6 +28,7 @@
qml/qmldir
qml/GzColor.qml
+ qml/GzPose.qml
qml/IgnSnackBar.qml
qml/IgnSpinBox.qml