#include "VrVirtualCameraWidget.h" #include "VrCameraSimulator.h" #include #include #include #include #include #include #include #include #include VrVirtualCameraWidget::VrVirtualCameraWidget(QWidget* parent) : QWidget(parent) { m_simulator = new VrCameraSimulator(this); InitUI(); // Connect simulator signals connect(m_simulator, &VrCameraSimulator::StatusChanged, this, &VrVirtualCameraWidget::OnSimulatorStatus); connect(m_simulator, &VrCameraSimulator::ClientConnected, this, &VrVirtualCameraWidget::OnSimulatorClientConnected); connect(m_simulator, &VrCameraSimulator::ClientDisconnected, this, &VrVirtualCameraWidget::OnSimulatorClientDisconnected); connect(m_simulator, &VrCameraSimulator::ImageGenerated, this, &VrVirtualCameraWidget::OnSimulatorImage); connect(m_simulator, &VrCameraSimulator::ImageDirectoryChanged, this, &VrVirtualCameraWidget::OnImageDirectoryChanged); connect(m_simulator, &VrCameraSimulator::LaserDataDirectoryChanged, this, &VrVirtualCameraWidget::OnLaserDirectoryChanged); connect(m_simulator, &VrCameraSimulator::LogMessage, this, &VrVirtualCameraWidget::OnSimulatorLog); resize(1100, 750); setWindowTitle("VrVirtualCamera - Virtual Network Camera Simulator"); // Auto-start the virtual camera service when the app opens. QTimer::singleShot(0, this, [this]() { if (!m_running) OnStartStop(); }); } VrVirtualCameraWidget::~VrVirtualCameraWidget() { if (m_simulator) { m_simulator->Stop(); } } void VrVirtualCameraWidget::InitUI() { auto* mainLayout = new QVBoxLayout(this); // ===== Top: Control Panel ===== auto* ctrlGroup = new QGroupBox("Control"); auto* ctrlLayout = new QHBoxLayout(ctrlGroup); m_startStopBtn = new QPushButton("Start"); m_startStopBtn->setMinimumWidth(100); m_startStopBtn->setStyleSheet( "QPushButton { background-color: #4CAF50; color: white; font-weight: bold; " "padding: 8px 16px; border-radius: 4px; }" "QPushButton:hover { background-color: #45a049; }"); ctrlLayout->addWidget(m_startStopBtn); connect(m_startStopBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnStartStop); ctrlLayout->addSpacing(20); m_statusLabel = new QLabel("Stopped"); m_statusLabel->setStyleSheet("QLabel { font-weight: bold; color: #666; }"); ctrlLayout->addWidget(new QLabel("Status:")); ctrlLayout->addWidget(m_statusLabel); ctrlLayout->addSpacing(20); m_clientLabel = new QLabel("No client"); m_clientLabel->setStyleSheet("QLabel { color: #999; }"); ctrlLayout->addWidget(new QLabel("Client:")); ctrlLayout->addWidget(m_clientLabel); ctrlLayout->addSpacing(20); m_ipLineEdit = new QLineEdit("192.168.1.200"); m_ipLineEdit->setReadOnly(true); m_ipLineEdit->setMaximumWidth(120); ctrlLayout->addWidget(new QLabel("Device IP:")); ctrlLayout->addWidget(m_ipLineEdit); ctrlLayout->addStretch(); mainLayout->addWidget(ctrlGroup); // ===== Configuration ===== auto* cfgGroup = new QGroupBox("Configuration"); auto* cfgLayout = new QHBoxLayout(cfgGroup); auto* resoLayout = new QFormLayout(); m_widthSpinBox = new QSpinBox(); m_widthSpinBox->setRange(320, 4096); m_widthSpinBox->setValue(1280); m_widthSpinBox->setSingleStep(64); resoLayout->addRow("Width:", m_widthSpinBox); connect(m_widthSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &VrVirtualCameraWidget::OnWidthChanged); m_heightSpinBox = new QSpinBox(); m_heightSpinBox->setRange(240, 4096); m_heightSpinBox->setValue(960); m_heightSpinBox->setSingleStep(64); resoLayout->addRow("Height:", m_heightSpinBox); connect(m_heightSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &VrVirtualCameraWidget::OnHeightChanged); cfgLayout->addLayout(resoLayout); cfgLayout->addSpacing(30); auto* fpsLayout = new QFormLayout(); m_fpsSpinBox = new QSpinBox(); m_fpsSpinBox->setRange(1, 120); m_fpsSpinBox->setValue(30); fpsLayout->addRow("Frame Rate:", m_fpsSpinBox); connect(m_fpsSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &VrVirtualCameraWidget::OnFrameRateChanged); cfgLayout->addLayout(fpsLayout); cfgLayout->addStretch(); mainLayout->addWidget(cfgGroup); // ===== Data Source Panel ===== auto* dataGroup = new QGroupBox("Data Sources"); auto* dataLayout = new QVBoxLayout(dataGroup); // Image directory row auto* imgRow = new QHBoxLayout(); imgRow->addWidget(new QLabel("Images:")); m_imageDirEdit = new QLineEdit(); m_imageDirEdit->setPlaceholderText("Select directory with *-L.png / *-R.png pairs..."); m_imageDirEdit->setReadOnly(true); imgRow->addWidget(m_imageDirEdit, 1); auto* imgBrowseBtn = new QPushButton("Browse..."); connect(imgBrowseBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnBrowseImageDir); imgRow->addWidget(imgBrowseBtn); m_imageCountLabel = new QLabel("0 pairs"); m_imageCountLabel->setStyleSheet("QLabel { color: #888; }"); m_imageCountLabel->setMinimumWidth(80); imgRow->addWidget(m_imageCountLabel); dataLayout->addLayout(imgRow); // Laser data directory row auto* laserRow = new QHBoxLayout(); laserRow->addWidget(new QLabel("Laser:")); m_laserDirEdit = new QLineEdit(); m_laserDirEdit->setPlaceholderText("Select directory with .txt / .dat laser data files..."); m_laserDirEdit->setReadOnly(true); laserRow->addWidget(m_laserDirEdit, 1); auto* laserBrowseBtn = new QPushButton("Browse..."); connect(laserBrowseBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnBrowseLaserDir); laserRow->addWidget(laserBrowseBtn); m_laserCountLabel = new QLabel("0 files / 0 lines"); m_laserCountLabel->setStyleSheet("QLabel { color: #888; }"); m_laserCountLabel->setMinimumWidth(120); laserRow->addWidget(m_laserCountLabel); dataLayout->addLayout(laserRow); mainLayout->addWidget(dataGroup); // ===== Image Preview + Log ===== auto* splitter = new QSplitter(Qt::Horizontal); auto* imageGroup = new QGroupBox("Image Preview (Left / Right)"); auto* imageLayout = new QHBoxLayout(imageGroup); m_leftImageLabel = new QLabel("Left"); m_leftImageLabel->setAlignment(Qt::AlignCenter); m_leftImageLabel->setMinimumSize(320, 240); m_leftImageLabel->setStyleSheet("QLabel { border: 1px solid #ccc; background: #222; color: #888; }"); imageLayout->addWidget(m_leftImageLabel); m_rightImageLabel = new QLabel("Right"); m_rightImageLabel->setAlignment(Qt::AlignCenter); m_rightImageLabel->setMinimumSize(320, 240); m_rightImageLabel->setStyleSheet("QLabel { border: 1px solid #ccc; background: #222; color: #888; }"); imageLayout->addWidget(m_rightImageLabel); splitter->addWidget(imageGroup); auto* logGroup = new QGroupBox("Protocol Log"); auto* logLayout = new QVBoxLayout(logGroup); m_logEdit = new QTextEdit(); m_logEdit->setReadOnly(true); m_logEdit->setStyleSheet("QTextEdit { background: #1e1e1e; color: #d4d4d4; " "font-family: Consolas, monospace; font-size: 12px; }"); logLayout->addWidget(m_logEdit); splitter->addWidget(logGroup); splitter->setStretchFactor(0, 3); splitter->setStretchFactor(1, 2); mainLayout->addWidget(splitter, 1); } // ===== Slots ===== void VrVirtualCameraWidget::ApplyConfig() { if (!m_simulator) return; m_simulator->SetImageWidth(m_widthSpinBox->value()); m_simulator->SetImageHeight(m_heightSpinBox->value()); m_simulator->SetFrameRate(m_fpsSpinBox->value()); } void VrVirtualCameraWidget::OnStartStop() { if (m_running) { m_simulator->Stop(); m_running = false; m_startStopBtn->setText("Start"); m_startStopBtn->setStyleSheet( "QPushButton { background-color: #4CAF50; color: white; font-weight: bold; " "padding: 8px 16px; border-radius: 4px; }" "QPushButton:hover { background-color: #45a049; }"); } else { ApplyConfig(); if (m_simulator->Start()) { m_running = true; m_startStopBtn->setText("Stop"); m_startStopBtn->setStyleSheet( "QPushButton { background-color: #f44336; color: white; font-weight: bold; " "padding: 8px 16px; border-radius: 4px; }" "QPushButton:hover { background-color: #d32f2f; }"); } } } void VrVirtualCameraWidget::OnBrowseImageDir() { QString dir = QFileDialog::getExistingDirectory(this, "Select Image Directory", m_imageDirEdit->text()); if (!dir.isEmpty()) { m_imageDirEdit->setText(dir); if (m_simulator) m_simulator->SetImageDirectory(dir); } } void VrVirtualCameraWidget::OnBrowseLaserDir() { QString dir = QFileDialog::getExistingDirectory(this, "Select Laser Data Directory", m_laserDirEdit->text()); if (!dir.isEmpty()) { m_laserDirEdit->setText(dir); if (m_simulator) m_simulator->SetLaserDataDirectory(dir); } } void VrVirtualCameraWidget::OnFrameRateChanged(int fps) { if (m_simulator) m_simulator->SetFrameRate(fps); } void VrVirtualCameraWidget::OnWidthChanged(int w) { if (m_simulator) m_simulator->SetImageWidth(w); } void VrVirtualCameraWidget::OnHeightChanged(int h) { if (m_simulator) m_simulator->SetImageHeight(h); } void VrVirtualCameraWidget::OnSimulatorImage(const QImage& left, const QImage& right) { if (!left.isNull()) { QPixmap pm = QPixmap::fromImage(left.scaled( m_leftImageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_leftImageLabel->setPixmap(pm); } if (!right.isNull()) { QPixmap pm = QPixmap::fromImage(right.scaled( m_rightImageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_rightImageLabel->setPixmap(pm); } } void VrVirtualCameraWidget::OnImageDirectoryChanged(int count) { m_imageCountLabel->setText(QString("%1 pairs").arg(count)); m_imageCountLabel->setStyleSheet( count > 0 ? "QLabel { color: #4CAF50; font-weight: bold; }" : "QLabel { color: #888; }"); } void VrVirtualCameraWidget::OnLaserDirectoryChanged(int fileCount, int totalLines) { m_laserCountLabel->setText(QString("%1 files / %2 lines").arg(fileCount).arg(totalLines)); m_laserCountLabel->setStyleSheet( fileCount > 0 ? "QLabel { color: #4CAF50; font-weight: bold; }" : "QLabel { color: #888; }"); } void VrVirtualCameraWidget::OnSimulatorLog(const QString& msg, bool isWarning) { AppendLog(msg, isWarning); } void VrVirtualCameraWidget::OnSimulatorStatus(const QString& status) { m_statusLabel->setText(status); } void VrVirtualCameraWidget::OnSimulatorClientConnected(const QString& ip) { m_clientLabel->setText("Connected: " + ip); m_clientLabel->setStyleSheet("QLabel { color: #4CAF50; font-weight: bold; }"); } void VrVirtualCameraWidget::OnSimulatorClientDisconnected() { m_clientLabel->setText("No client"); m_clientLabel->setStyleSheet("QLabel { color: #999; }"); } void VrVirtualCameraWidget::AppendLog(const QString& msg, bool isWarning) { QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz"); QString color = isWarning ? "#ff6b6b" : "#d4d4d4"; m_logEdit->append(QString("[%1] " "%3") .arg(timestamp, color, msg.toHtmlEscaped())); QScrollBar* sb = m_logEdit->verticalScrollBar(); sb->setValue(sb->maximum()); }