#include "MainPanel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "network/websocketclient.h" #include #include #include #include #include #include #include "widgets/bubbletip.h" #include "widgets/chatView/chatwindow.h" #include #include #include #include #include "AVPlayer/avplayerwidget.h" #include "api/roomapi.h" #include "appevent.h" #include "widgets/framelessbase.h" #include "widgets/functionbutton.h" #include "widgets/maskoverlay.h" #include "widgets/recorderwidget.h" #include "widgets/statswidget.h" // 新增:设置面板 #include "themesettingswidget.h" namespace IconUtils { QIcon createSettingsIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(60,60,60), 2)); p.drawEllipse(QPointF(12,12), 8, 8); p.drawLine(QPointF(12,2), QPointF(12,6)); p.drawLine(QPointF(12,22), QPointF(12,18)); p.end(); return QIcon(pix); } QIcon createSearchIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(60,60,60), 2)); p.drawEllipse(QPointF(10,10), 6, 6); p.drawLine(QPointF(14,14), QPointF(22,22)); p.end(); return QIcon(pix); } QIcon createUserIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(60,60,60), 2)); p.drawEllipse(QPointF(12,9), 5, 5); p.drawRoundedRect(QRectF(6,14,12,8), 4, 4); p.end(); return QIcon(pix); } QIcon createAudioDeviceIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(60,60,60), 2)); p.drawRoundedRect(QRectF(5,6,14,12), 3, 3); p.drawLine(QPointF(12,18), QPointF(12,22)); p.drawLine(QPointF(8,22), QPointF(16,22)); p.end(); return QIcon(pix); } QIcon createStreamIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(250,80,60), 2)); p.setBrush(QColor(250,80,60)); QPolygon tri; tri << QPoint(8,6) << QPoint(20,12) << QPoint(8,18); p.drawPolygon(tri); p.end(); return QIcon(pix); } QIcon createChatIcon() { QPixmap pix(24, 24); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(QColor(60,60,60), 2)); p.setBrush(Qt::NoBrush); p.drawRoundedRect(QRectF(4,5,16,12), 3, 3); QPolygon tail; tail << QPoint(12,17) << QPoint(10,21) << QPoint(15,18); p.drawPolygon(tail); p.end(); return QIcon(pix); } } // namespace IconUtils MainPanel::MainPanel(QWidget *parent) : QWidget(parent) , chatView(nullptr) { setAttribute(Qt::WA_StyledBackground, true); setStyleSheet("background: #f7f7f7;"); m_debounceTimer = new QTimer(this); m_debounceTimer->setInterval(500); m_debounceTimer->setSingleShot(true); connect(m_debounceTimer, &QTimer::timeout, this, &MainPanel::handleDebouncedPlay); webSocketClient = new WebSocketClient(this); // 启用WebSocket自动重连,提升聊天室稳定性 if (webSocketClient) { webSocketClient->setAutoReconnect(true); } chatView = new ChatWindow(webSocketClient); chatView->setMinimumWidth(400); // 防御:明确不随关闭销毁,避免父窗口关闭时误删 chatView->setAttribute(Qt::WA_DeleteOnClose, false); // 连接聊天窗口关闭请求信号 connect(chatView, &ChatWindow::windowCloseRequested, this, &MainPanel::onChatWindowCloseRequested); // statsWidget = new StatsWidget(this); // 暂时移除统计在此处的展示 m_rightWidget = new QWidget; QVBoxLayout *vbox = new QVBoxLayout(m_rightWidget); vbox->setContentsMargins(0, 0, 0, 0); // vbox->addWidget(userProfile, 0); // vbox->addWidget(statsWidget, 0); // 暂时移除 // 创建聊天窗口容器 m_chatContainer = new QWidget(m_rightWidget); QVBoxLayout *chatLayout = new QVBoxLayout(m_chatContainer); chatLayout->setContentsMargins(0, 0, 0, 0); chatLayout->addWidget(chatView); vbox->addWidget(m_chatContainer, 1); splitter = new QSplitter(Qt::Horizontal, this); playerContainer = new QWidget(this); splitter->addWidget(playerContainer); splitter->addWidget(m_rightWidget); splitter->setStretchFactor(0, 60); splitter->setStretchFactor(1, 30); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(splitter, 1); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); setLayout(mainLayout); // 为playerContainer设置初始布局 QVBoxLayout *playerLayout = new QVBoxLayout(playerContainer); playerLayout->setContentsMargins(0, 0, 0, 0); buttonGroup = new PopoverButtonGroup(Qt::Horizontal, playerContainer); // 添加功能按钮 FunctionButton *settingsBtn = new FunctionButton(IconUtils::createSettingsIcon(), "设置", this); // 移除 Popover 箭头,改为直接打开设置窗口 buttonGroup->addButton(settingsBtn, nullptr); connect(settingsBtn, &QPushButton::clicked, this, &MainPanel::onSettingsButtonClicked); // 移除:搜索按钮(暂不使用) // FunctionButton *searchBtn = new FunctionButton(IconUtils::createSearchIcon(), "搜索", this); // Popover *searchPopover = new Popover(this); // buttonGroup->addButton(searchBtn, searchPopover); FunctionButton *userBtn = new FunctionButton(IconUtils::createUserIcon(), "用户", this); Popover *userPopover = new Popover(this); // 构建用户Popover内容:提供“退出”操作 { QWidget *userContent = new QWidget(userPopover); QVBoxLayout *userLayout = new QVBoxLayout(userContent); userLayout->setContentsMargins(8, 8, 8, 8); userLayout->setSpacing(8); QPushButton *logoutBtn = new QPushButton(tr("退出"), userContent); logoutBtn->setMinimumWidth(120); connect(logoutBtn, &QPushButton::clicked, this, [this]() { emit logoutClicked(); }); userLayout->addWidget(logoutBtn); userPopover->setContentWidget(userContent); } buttonGroup->addButton(userBtn, userPopover); // 添加音频设备选择按钮 FunctionButton *audioDeviceBtn = new FunctionButton(IconUtils::createAudioDeviceIcon(), "音频设备", this); Popover *audioDevicePopover = new Popover(this); // // 使用解耦合版本的音频设备选择器 // m_audioDeviceSelectorDecoupled = new AudioDeviceSelectorIconDecoupled(this); // audioDevicePopover->setContentWidget(m_audioDeviceSelectorDecoupled); // 使用 RecorderAudioWidget 作为 Popover 内容(麦克风 + 扬声器) QWidget *audioContent = new QWidget(audioDevicePopover); QVBoxLayout *audioLayout = new QVBoxLayout(audioContent); audioLayout->setContentsMargins(8, 8, 8, 8); audioLayout->setSpacing(8); // 麦克风区域 QLabel *micTitle = new QLabel(tr("麦克风"), audioContent); micTitle->setStyleSheet("font-weight:600;"); m_micWidget = new QComboBox(audioContent); m_micWidget->setEditable(false); audioLayout->addWidget(micTitle); audioLayout->addWidget(m_micWidget); // 扬声器区域 QLabel *speakerTitle = new QLabel(tr("扬声器"), audioContent); speakerTitle->setStyleSheet("font-weight:600;"); m_speakerWidget = new QComboBox(audioContent); m_speakerWidget->setEditable(false); audioLayout->addWidget(speakerTitle); audioLayout->addWidget(m_speakerWidget); // 视频编码器区域 QLabel *encoderTitle = new QLabel(tr("视频编码器"), audioContent); encoderTitle->setStyleSheet("font-weight:600;"); m_encoderWidget = new QComboBox(audioContent); m_encoderWidget->setEditable(false); audioLayout->addWidget(encoderTitle); audioLayout->addWidget(m_encoderWidget); audioDevicePopover->setContentWidget(audioContent); buttonGroup->addButton(audioDeviceBtn, audioDevicePopover); // 新增:独立的推流按钮(无弹层) m_streamButton = new FunctionButton(IconUtils::createStreamIcon(), tr("推流"), this); buttonGroup->addButton(m_streamButton); // 新增:聊天按钮(推流:显示/隐藏;非推流:弹出/嵌入) m_chatButton = new FunctionButton(IconUtils::createChatIcon(), tr("聊天"), this); buttonGroup->addButton(m_chatButton); // 移除:执行操作按钮(暂不使用) // FunctionButton *actionButton = new FunctionButton(IconUtils::createSettingsIcon(), // "执行操作", // this); // buttonGroup->addButton(actionButton, nullptr); // 将buttonGroup添加到playerContainer的布局中 playerLayout = qobject_cast(playerContainer->layout()); if (!playerLayout) { playerLayout = new QVBoxLayout(playerContainer); playerLayout->setContentsMargins(0, 0, 0, 0); } playerLayout->addStretch(1); // 添加弹性空间,将buttonGroup推到底部 playerLayout->addWidget(buttonGroup, 0); // 添加buttonGroup到底部,不拉伸 buttonGroup->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 使用固定大小策略 // initConnect // 已移除与 UserProfileWidget 相关的在线状态更新 // connect(webSocketClient, &WebSocketClient::statsUpdate, statsWidget, &StatsWidget::updateStats); // 暂时移除统计在主面板的更新 connect(webSocketClient, &WebSocketClient::liveStatus, this, [this](const QString &msg) { // 这里可以处理 liveStatus 相关逻辑 QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(msg.toUtf8(), &err); if (err.error != QJsonParseError::NoError || !doc.isObject()) { qDebug() << "[MainPanel] liveStatus: 解析失败" << err.errorString(); return; } QJsonObject obj = doc.object(); int liveStatus = obj.value("liveStatus").toInt(0); // 默认-1 if (liveStatus == 1) { qDebug() << "[MainPanel] liveStatus: 直播中" << chatView; if (chatView) { const QString id = webSocketClient->roomId(); // 使用防抖机制处理频繁的请求 m_pendingRoomId = id; m_debounceTimer->start(); // 重新开始计时,如果在500ms内再次收到请求,会重置定时器 } } }); // 连接按钮事件 connect(m_streamButton, &QPushButton::clicked, this, &MainPanel::onStreamButtonClicked); connect(m_chatButton, &QPushButton::clicked, this, &MainPanel::onChatButtonClicked); // 初始化音频设备列表 initAudioDeviceSelectors(); } MainPanel::~MainPanel() { if (m_avPlayerStandalone) { m_avPlayerStandalone->deleteLater(); m_avPlayerStandalone = nullptr; } if (m_recorderStandalone) { m_recorderStandalone->deleteLater(); m_recorderStandalone = nullptr; } } void MainPanel::setRole(const QStringList &roleList) { bool isRec = roleList.contains("role.admin") || roleList.contains("role.recorder") || roleList.contains("录制"); if (isRec) { auto rec = new RecorderWidget(this); setPlayerWidget(rec); } else { auto av = new AVPlayerWidget(this); setPlayerWidget(av); } if (m_streamButton) { m_streamButton->setVisible(isRec); m_streamButton->setText(tr("推流")); } } void MainPanel::setPushRoomId(const QString &id) { if (!webSocketClient) return; // 初始化聊天室连接 webSocketClient->connectToRoom(id); if (chatView) { chatView->initWebsocket(id); } // 若当前是播放器,使用防抖播放 m_pendingRoomId = id; m_debounceTimer->start(); } void MainPanel::setPlayerWidget(QWidget *newPlayer) { if (!newPlayer) return; if (!playerContainer) return; if (playerWidget) { playerWidget->deleteLater(); playerWidget = nullptr; } playerWidget = newPlayer; // 将新播放器添加到容器布局 if (auto layout = qobject_cast(playerContainer->layout())) { layout->insertWidget(0, playerWidget, 1); } else { auto layout2 = new QVBoxLayout(playerContainer); layout2->setContentsMargins(0, 0, 0, 0); layout2->setSpacing(0); layout2->addWidget(playerWidget, 1); } // 更新推流按钮可见性 if (m_streamButton) { const bool isRec = qobject_cast(playerWidget) != nullptr; m_streamButton->setVisible(isRec); if (!isRec) { m_isStreaming = false; m_streamButton->setText(tr("推流")); } } // 连接 RecorderWidget 的推流信号,保持 UI 状态同步 if (auto rec = qobject_cast(playerWidget)) { connect(rec, &RecorderWidget::streamingStarted, this, [this, rec]() { m_isStreaming = true; if (m_streamButton) m_streamButton->setText(tr("停止推流")); // 进入极简模式 if (QWidget *tlw = window()) { tlw->setUpdatesEnabled(false); } rec->hidePreview(); if (chatView) chatView->hide(); if (m_rightWidget) m_rightWidget->hide(); if (splitter) splitter->hide(); // 使用自定义标题的紧凑浮窗承载工具栏 if (!m_compactFrame) { m_compactFrame = new TMainWindow(); m_compactFrame->setWindowTitle(tr("共享控制")); m_compactFrame->setWindowFlag(Qt::Tool, true); // m_compactFrame->setAttribute(Qt::WA_DeleteOnClose); if (buttonGroup) { if (auto layout = qobject_cast(playerContainer->layout())) { layout->removeWidget(buttonGroup); } auto container = new QWidget(m_compactFrame); auto lay = new QHBoxLayout(container); lay->setContentsMargins(12, 8, 12, 8); lay->setSpacing(10); buttonGroup->setParent(container); lay->addWidget(buttonGroup); m_compactFrame->setCentralWidget(container); // 使用 sizeHint 直接设置初始尺寸,避免反复 adjustSize 带来的重算 const QSize hint = buttonGroup->sizeHint(); int w = qMax(360, hint.width() + 24); int h = qMax(80, hint.height() + 16); int titleH = 0; if (auto mw = m_compactFrame->menuWidget()) { mw->adjustSize(); titleH = mw->sizeHint().height(); } m_compactFrame->setMinimumSize(w, h + titleH); m_compactFrame->resize(w, h + titleH); } } m_compactFrame->show(); m_compactFrame->raise(); m_compactMode = true; // 主窗口保持原几何但隐藏 if (QWidget *tlw = window()) { m_savedWindowGeometry = tlw->geometry(); tlw->hide(); } }); connect(rec, &RecorderWidget::streamingStopped, this, [this, rec]() { m_isStreaming = false; if (m_streamButton) m_streamButton->setText(tr("推流")); // 退出极简模式 if (QWidget *tlw = window()) { tlw->setUpdatesEnabled(false); } rec->showPreview(); if (splitter) splitter->show(); if (m_rightWidget) m_rightWidget->show(); // 销毁紧凑浮窗并将 buttonGroup 归还 if (m_compactFrame) { if (buttonGroup) { buttonGroup->hide(); buttonGroup->setParent(playerContainer); if (auto layout = qobject_cast(playerContainer->layout())) { layout->addWidget(buttonGroup, 0); } buttonGroup->show(); } m_compactFrame->close(); m_compactFrame->deleteLater(); m_compactFrame = nullptr; } m_compactMode = false; // 恢复主窗口显示 if (QWidget *tlw = window()) { tlw->setGeometry(m_savedWindowGeometry); tlw->show(); tlw->raise(); tlw->setUpdatesEnabled(true); } showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); applyModeLayout(); // 恢复布局为非推流状态 }); } // 根据当前模式(录制/播放)应用一次自适应布局 applyModeLayout(); } void MainPanel::handleDebouncedPlay() { // 在定时器触发时执行播放逻辑 const QString id = m_pendingRoomId; if (id.isEmpty()) return; qDebug() << "[MainPanel] Debounced startPlay for room" << id; AVPlayerWidget *av = qobject_cast(playerWidget); if (av) { av->stopPlay(); av->setPlayRoomId(id); av->startPlay(); return; } RecorderWidget *rec = qobject_cast(playerWidget); if (rec) { // rec->startLive(); // - RecorderWidget::Settings s = rec->m_settings; // 需要通过公共接口设置,避免直接访问成员 } } void MainPanel::initAudioDeviceSelectors() { // TODO: 枚举音频设备、填充 m_micWidget 和 m_speakerWidget;枚举编码器填充 m_encoderWidget } void MainPanel::showRecorderStandalone() { if (m_recorderFrame) { m_recorderFrame->raise(); m_recorderFrame->activateWindow(); return; } m_recorderFrame = new TMainWindow(); m_recorderFrame->setAttribute(Qt::WA_DeleteOnClose); m_recorderFrame->setWindowTitle(tr("录制器")); m_recorderStandalone = new RecorderWidget(m_recorderFrame); m_recorderFrame->setCentralWidget(m_recorderStandalone); m_recorderFrame->resize(960, 600); m_recorderFrame->show(); connect(m_recorderFrame, &QObject::destroyed, this, [this]() { m_recorderFrame = nullptr; m_recorderStandalone = nullptr; }); } void MainPanel::showPlayerStandalone() { if (m_playerFrame) { m_playerFrame->raise(); m_playerFrame->activateWindow(); return; } m_playerFrame = new TMainWindow(); m_playerFrame->setAttribute(Qt::WA_DeleteOnClose); m_playerFrame->setWindowTitle(tr("播放器")); m_avPlayerStandalone = new AVPlayerWidget(m_playerFrame); m_playerFrame->setCentralWidget(m_avPlayerStandalone); m_playerFrame->resize(960, 600); m_playerFrame->show(); connect(m_playerFrame, &QObject::destroyed, this, [this]() { m_playerFrame = nullptr; m_avPlayerStandalone = nullptr; }); } void MainPanel::showChatStandalone() { if (!chatView) return; if (m_isStreaming) return; // 推流时保持仅浮窗 // - if (m_chatFrame) { // - m_chatFrame->raise(); // - m_chatFrame->activateWindow(); // - return; // - } if (m_chatFrame) { // 确保 chatView 已正确作为中央部件挂载 if (m_chatFrame->centralWidget() != chatView && chatView) { chatView->hide(); if (chatView->parent() != m_chatFrame) { chatView->setParent(m_chatFrame); } m_chatFrame->setCentralWidget(chatView); chatView->show(); } if (!m_chatFrame->isVisible()) m_chatFrame->show(); m_chatFrame->raise(); m_chatFrame->activateWindow(); return; } // 从嵌入容器移除 if (m_chatContainer) { if (auto l = qobject_cast(m_chatContainer->layout())) { l->removeWidget(chatView); } } m_chatFrame = new TMainWindow(); m_chatFrame->setAttribute(Qt::WA_DeleteOnClose); m_chatFrame->setWindowTitle(tr("聊天")); m_chatFrame->installEventFilter(this); chatView->setParent(m_chatFrame); m_chatFrame->setCentralWidget(chatView); m_chatFrame->resize(380, 540); m_chatFrame->show(); // - connect(m_chatFrame, &QObject::destroyed, this, [this]() { // - m_chatFrame = nullptr; // - onChatWindowCloseRequested(); // - }); connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; }); } void MainPanel::showChatEmbedded() { if (!chatView || !m_chatContainer) return; // 如存在独立窗口包装,先将 chatView 移回容器再关闭窗口,避免被父窗口销毁 if (m_chatFrame) { // 断开临时回调,避免 destroyed 中的副作用 disconnect(m_chatFrame, nullptr, this, nullptr); if (chatView->parent() == m_chatFrame) { // 使用 takeCentralWidget 而非 setCentralWidget(nullptr) 避免误删中央部件 if (m_chatFrame->centralWidget()) { QWidget *w = m_chatFrame->takeCentralWidget(); Q_UNUSED(w); } } chatView->hide(); chatView->setParent(m_chatContainer); chatView->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(chatView) < 0) l->addWidget(chatView); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(chatView); } chatView->show(); // 现在安全地关闭独立窗口 m_chatFrame->close(); m_chatFrame = nullptr; } else { // 无独立窗口,仅确保嵌入 chatView->hide(); chatView->setParent(m_chatContainer); chatView->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(chatView) < 0) l->addWidget(chatView); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(chatView); } chatView->show(); } } void MainPanel::onRecordButtonClicked() { // TODO: 录制控制逻辑 } void MainPanel::onStreamButtonClicked() { RecorderWidget *rec = qobject_cast(playerWidget); if (!rec) { qDebug() << "[MainPanel] 当前不处于录制器模式,无法推流"; return; } if (!m_isStreaming) { rec->startStreaming(); // 成功与否由信号驱动 UI } else { rec->stopStreaming(); } } void MainPanel::onSettingsButtonClicked() { // 使用 TMainWindow 包裹设置页面,保持无边框一致风格 if (m_settingsFrame) { if (m_settingsFrame->isVisible()) { m_settingsFrame->raise(); m_settingsFrame->activateWindow(); } else { m_settingsFrame->show(); m_settingsFrame->raise(); m_settingsFrame->activateWindow(); } return; } m_settingsFrame = new TMainWindow(); m_settingsFrame->setAttribute(Qt::WA_DeleteOnClose); m_settingsFrame->setWindowTitle(tr("设置")); // 当设置窗口关闭时,清空指针 connect(m_settingsFrame, &QObject::destroyed, this, [this]() { m_settingsFrame = nullptr; }); auto *settingsWidget = new ThemeSettingsWidget(m_settingsFrame); m_settingsFrame->setCentralWidget(settingsWidget); m_settingsFrame->resize(560, 460); m_settingsFrame->show(); } void MainPanel::onChatWindowCloseRequested() { // 如果处在推流状态,认为是“隐藏聊天”而不是关闭程序 if (m_isStreaming) { // 显式隐藏独立聊天窗口,统一行为到 eventFilter 的隐藏逻辑 if (m_chatFrame && m_chatFrame->isVisible()) { m_chatFrame->hide(); } if (m_chatButton) m_chatButton->setText(tr("显示聊天")); return; // ChatWindow 本身不要销毁 } // 非推流:回归嵌入显示 showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); } void MainPanel::onChatButtonClicked() { if (!chatView) return; if (m_isStreaming) { // 推流时:只在独立聊天窗口上 显示/隐藏 切换,避免频繁 reparent if (!m_chatFrame) { // 如果当前在嵌入容器里,先从布局移除 if (m_chatContainer) { if (auto l = qobject_cast(m_chatContainer->layout())) { l->removeWidget(chatView); } } m_chatFrame = new TMainWindow(); m_chatFrame->setAttribute(Qt::WA_DeleteOnClose); m_chatFrame->setWindowTitle(tr("聊天")); m_chatFrame->installEventFilter(this); chatView->setParent(m_chatFrame); m_chatFrame->setCentralWidget(chatView); m_chatFrame->resize(380, 540); m_chatFrame->show(); connect(m_chatFrame, &QObject::destroyed, this, [this]() { m_chatFrame = nullptr; }); if (m_chatButton) m_chatButton->setText(tr("隐藏聊天")); return; // 结束推流分支处理 } else { // 已有独立窗口:切换显示/隐藏 if (chatView->parent() != m_chatFrame) { chatView->setParent(m_chatFrame); if (m_chatFrame->centralWidget() != chatView) m_chatFrame->setCentralWidget(chatView); } if (m_chatFrame->isVisible()) { m_chatFrame->hide(); if (m_chatButton) m_chatButton->setText(tr("显示聊天")); } else { m_chatFrame->show(); m_chatFrame->raise(); m_chatFrame->activateWindow(); if (m_chatButton) m_chatButton->setText(tr("隐藏聊天")); } return; // 结束推流分支处理 } } // 非推流:在弹出与嵌入间切换 if (m_chatFrame) { // 已经是弹出状态,切回嵌入 showChatEmbedded(); if (m_chatButton) m_chatButton->setText(tr("聊天")); } else { // 目前为嵌入状态,弹出为独立窗口 showChatStandalone(); if (m_chatButton) m_chatButton->setText(tr("嵌入聊天")); } } // ===== 浮动工具栏 ===== void MainPanel::showFloatingToolbar() { // 改为使用 m_compactFrame 承载,无需再在主窗口中悬浮 if (!m_compactFrame) return; m_compactFrame->show(); m_compactFrame->raise(); } void MainPanel::hideFloatingToolbar() { if (m_compactFrame) m_compactFrame->hide(); } // 新增:根据模式与窗口大小自适应布局 void MainPanel::applyModeLayout() { // 若控件未初始化,直接返回 if (!splitter || !playerContainer) return; // 推流期间,主窗口处于隐藏/极简状态,这里只保证分割区域隐藏 if (m_isStreaming) { if (m_rightWidget) m_rightWidget->hide(); splitter->hide(); return; } const bool isRecorder = qobject_cast(playerWidget) != nullptr; const bool isPlayer = qobject_cast(playerWidget) != nullptr; // 保证分割窗口可见,用于布局 splitter->show(); const int panelW = width() > 0 ? width() : 1200; if (isPlayer) { // 播放模式:默认折叠右侧,让视频获得更大可视区域 if (m_rightWidget) m_rightWidget->hide(); // 如果聊天仍处于嵌入容器,将其弹出为独立窗口,避免占位 if (chatView && m_chatContainer && chatView->parent() == m_chatContainer) { showChatStandalone(); } // 左侧铺满 QList sizes; sizes << panelW << 0; splitter->setSizes(sizes); return; } // 录制模式(非推流):展示右侧面板并保持聊天嵌入,宽度自适应为总宽的 1/3(300~420 间) if (m_rightWidget) m_rightWidget->show(); showChatEmbedded(); int chatW = panelW / 3; chatW = qBound(300, chatW, 420); const int leftW = qMax(0, panelW - chatW); QList sizes; sizes << leftW << chatW; splitter->setSizes(sizes); } void MainPanel::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); // 随窗口大小调整左右比例 applyModeLayout(); } bool MainPanel::eventFilter(QObject *watched, QEvent *event) { // 拦截聊天独立窗口的关闭事件 if (watched == m_chatFrame && event->type() == QEvent::Close) { // 推流期间:关闭操作改为隐藏窗口,避免销毁与频繁 reparent 导致异常 if (m_isStreaming) { event->ignore(); if (m_chatFrame) { m_chatFrame->hide(); } if (m_chatButton) m_chatButton->setText(tr("显示聊天")); return true; // 事件已处理,不再继续关闭 } // 非推流:允许关闭,但先把 chatView 放回嵌入容器,避免被父窗口销毁 if (chatView && m_chatContainer && chatView->parent() == m_chatFrame) { // 取走中央部件,避免 setCentralWidget(nullptr) 触发潜在删除 if (m_chatFrame->centralWidget()) { QWidget *w = m_chatFrame->takeCentralWidget(); Q_UNUSED(w); } chatView->hide(); chatView->setParent(m_chatContainer); chatView->setWindowTitle(QString()); if (auto l = qobject_cast(m_chatContainer->layout())) { if (l->indexOf(chatView) < 0) l->addWidget(chatView); } else { auto l2 = new QVBoxLayout(m_chatContainer); l2->setContentsMargins(0, 0, 0, 0); l2->addWidget(chatView); } chatView->show(); if (m_chatButton) m_chatButton->setText(tr("聊天")); } return false; // 继续关闭流程 } return QWidget::eventFilter(watched, event); }