Robot Monitor
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtRos2.Core
Rectangle {
id: root
radius: Theme.radius
color: Theme.card
border.color: Theme.border
border.width: 1
required property MainViewModel mainViewModel
function stateLabel(state) {
switch (state) {
case 1:
return "requested"
case 2:
return "active"
case 3:
return "rejected"
case 4:
return "canceled"
case 5:
return "succeeded"
case 6:
return "aborted"
default:
return "idle"
}
}
function stateColor(state) {
switch (state) {
case 2:
case 5:
return "#8fda72"
case 1:
return "#64b5f6"
case 3:
case 4:
case 6:
return "#ff8a65"
default:
return "#c0c0c0"
}
}
RowLayout {
anchors.fill: parent
anchors.margins: Theme.radius
spacing: Theme.spacingLarge
Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Subscribers"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
Label {
text: `${root.mainViewModel.connectedSubscribers}/${root.mainViewModel.totalSubscribers}`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
}
}
Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Publishers"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
Label {
text: `${root.mainViewModel.connectedPublishers}/${root.mainViewModel.totalPublishers}`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
}
}
Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Action Clients"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
Label {
text: `${root.mainViewModel.connectedActionClients}/${root.mainViewModel.totalActionClients}`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
Rectangle {
id: navStatus
Layout.preferredWidth: 500
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
function formatSeconds(duration) {
if (!duration)
return "--"
const sec = Number(
duration.sec !== undefined ? duration.sec : duration.seconds)
|| 0
const nsec = Number(
duration.nanosec !== undefined ? duration.nanosec : duration.nsec)
|| 0
const total = sec + nsec / 1e9
if (total <= 0)
return "--"
if (total >= 60)
return (total / 60).toFixed(1) + " min"
return total.toFixed(1) + " s"
}
function distanceText() {
return root.mainViewModel.navigationStatus
=== ActionClientBase.Accepted ? root.mainViewModel.navigationFeedback.distanceRemaining.toFixed(
2) + " m" : "---"
}
function navigationTimeText() {
return root.mainViewModel.navigationStatus
=== ActionClientBase.Accepted ? formatSeconds(
root.mainViewModel.navigationFeedback.navigationTime) : "--"
}
function etaText() {
return root.mainViewModel.navigationStatus
=== ActionClientBase.Accepted ? formatSeconds(
root.mainViewModel.navigationFeedback.estimatedTimeRemaining) : "--"
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Navigate to pose"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
RowLayout {
spacing: Theme.spacingSmall
Layout.fillWidth: true
Label {
text: `Status:`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
Label {
text: root.stateLabel(
root.mainViewModel.navigationStatus)
color: root.stateColor(
root.mainViewModel.navigationStatus)
font.pixelSize: 14
font.bold: true
Layout.fillWidth: true
}
Label {
text: `Remaing: ${navStatus.distanceText()}`
color: Theme.foreground
font.pixelSize: 14
Layout.preferredWidth: 110
horizontalAlignment: Qt.AlignLeft
font.bold: true
}
Label {
text: `ETA: ${navStatus.etaText()}`
color: Theme.foreground
font.pixelSize: 14
Layout.preferredWidth: 110
horizontalAlignment: Qt.AlignLeft
font.bold: true
}
Label {
text: `Time: ${navStatus.navigationTimeText()}`
color: Theme.foreground
font.pixelSize: 14
Layout.preferredWidth: 110
horizontalAlignment: Qt.AlignLeft
font.bold: true
}
}
}
}
Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Undock"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
RowLayout {
spacing: Theme.spacingSmall
Layout.fillWidth: true
Label {
text: `Status:`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
Label {
text: root.stateLabel(root.mainViewModel.undockStatus)
color: root.stateColor(root.mainViewModel.undockStatus)
font.pixelSize: 14
font.bold: true
Layout.fillWidth: true
}
}
}
}
Rectangle {
Layout.preferredWidth: 280
Layout.preferredHeight: 48
radius: Theme.radius
color: Theme.secondary
border.color: Theme.border
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSmall
spacing: 2
Label {
text: "Dock"
color: Theme.mutedForeground
font.pixelSize: 11
font.bold: true
}
RowLayout {
spacing: Theme.spacingSmall
Layout.fillWidth: true
Label {
text: `Status:`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
}
Label {
text: root.stateLabel(root.mainViewModel.dockStatus)
color: root.stateColor(root.mainViewModel.dockStatus)
font.pixelSize: 14
font.bold: true
Layout.fillWidth: true
}
Label {
text: `Sees dock: ${root.mainViewModel.dockStatus
=== ActionClientBase.Accepted ? root.mainViewModel.dockFeedback : "---"}`
color: Theme.foreground
font.pixelSize: 14
font.bold: true
Layout.preferredWidth: 110
}
}
}
}
}
}