diff --git a/src/citron/util/multiplayer_room_overlay.cpp b/src/citron/util/multiplayer_room_overlay.cpp new file mode 100644 index 000000000..020fd0766 --- /dev/null +++ b/src/citron/util/multiplayer_room_overlay.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "citron/main.h" +#include "citron/bootmanager.h" +#include "citron/util/multiplayer_room_overlay.h" +#include "network/network.h" +#include "network/room.h" + +MultiplayerRoomOverlay::MultiplayerRoomOverlay(GMainWindow* parent) + : QWidget(parent), main_window(parent) { + + setAttribute(Qt::WA_TranslucentBackground, true); + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint); + setFocusPolicy(Qt::ClickFocus); + + background_color = QColor(20, 20, 20, 180); + border_color = QColor(60, 60, 60, 120); + + main_layout = new QGridLayout(this); + main_layout->setContentsMargins(padding, padding, 0, 0); // No margins on bottom/right for grip + main_layout->setSpacing(6); + + players_online_label = new QLabel(this); + chat_room_widget = new ChatRoom(this); + size_grip = new QSizeGrip(this); + + players_online_label->setFont(QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold)); + players_online_label->setStyleSheet(QString::fromUtf8("color: #E0E0E0;")); + players_online_label->setText(QString::fromUtf8("Players Online: 0")); + players_online_label->setAttribute(Qt::WA_TransparentForMouseEvents, true); + + size_grip->setFixedSize(16, 16); + + if (main_window) { + GRenderWindow* render_window = main_window->findChild(); + if (render_window) { + render_window->installEventFilter(this); + } + } + + main_layout->addWidget(players_online_label, 0, 0, 1, 2); + main_layout->addWidget(chat_room_widget, 1, 0, 1, 2); + main_layout->addWidget(size_grip, 1, 1, 1, 1, Qt::AlignBottom | Qt::AlignRight); + + main_layout->setRowStretch(1, 1); + main_layout->setColumnStretch(0, 1); + + setLayout(main_layout); + + update_timer.setSingleShot(false); + connect(&update_timer, &QTimer::timeout, this, &MultiplayerRoomOverlay::UpdateRoomData); + + setMinimumSize(280, 220); + resize(320, 280); + UpdatePosition(); +} + +MultiplayerRoomOverlay::~MultiplayerRoomOverlay() { + DisconnectFromRoom(); +} + +void MultiplayerRoomOverlay::OnEmulationStarting() { + // When emulation starts, resume updates if we are visible. + if (is_visible) { + ConnectToRoom(); + update_timer.start(500); + } +} + +void MultiplayerRoomOverlay::OnEmulationStopping() { + // CRASH FIX: When emulation stops, immediately disconnect from network objects. + update_timer.stop(); + DisconnectFromRoom(); +} + +void MultiplayerRoomOverlay::SetVisible(bool visible) { + if (is_visible == visible) return; + is_visible = visible; + + if (visible) { + show(); + // Only start connecting and updating if emulation is running. + if (main_window && main_window->IsEmulationRunning()) { + ConnectToRoom(); + update_timer.start(500); + } + } else { + hide(); + update_timer.stop(); + DisconnectFromRoom(); + } +} + +void MultiplayerRoomOverlay::paintEvent(QPaintEvent* event) { + Q_UNUSED(event) + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + QPainterPath background_path; + background_path.addRoundedRect(rect(), corner_radius, corner_radius); + painter.fillPath(background_path, background_color); + painter.setPen(QPen(border_color, border_width)); + painter.drawPath(background_path); +} + +void MultiplayerRoomOverlay::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (!has_been_moved) UpdatePosition(); } +bool MultiplayerRoomOverlay::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { if (chat_room_widget->hasFocus()) { chat_room_widget->clearFocus(); } } return QObject::eventFilter(watched, event); } + +#if defined(Q_OS_LINUX) +void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + if (size_grip->geometry().contains(event->pos())) { + // Let the size grip handle the event + } else if (!childAt(event->pos()) || childAt(event->pos()) == this) { + if (windowHandle()) windowHandle()->startSystemMove(); + } + } + QWidget::mousePressEvent(event); +} +void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) { QWidget::mouseMoveEvent(event); } +#else +void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + if (size_grip->geometry().contains(event->pos())) { + // Let the size grip handle the event + } else if (!childAt(event->pos()) || childAt(event->pos()) == this) { + is_dragging = true; + drag_start_pos = event->globalPosition().toPoint(); + widget_start_pos = this->pos(); + setCursor(Qt::ClosedHandCursor); + } + } + QWidget::mousePressEvent(event); +} +void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) { + if (is_dragging) { + QPoint delta = event->globalPosition().toPoint() - drag_start_pos; + move(widget_start_pos + delta); + } + QWidget::mouseMoveEvent(event); // Corrected typo here +} +#endif + +void MultiplayerRoomOverlay::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton && is_dragging) { + is_dragging = false; + has_been_moved = true; + setCursor(Qt::ArrowCursor); + } + QWidget::mouseReleaseEvent(event); +} + +void MultiplayerRoomOverlay::ConnectToRoom() { + if (!main_window) return; + multiplayer_state = main_window->GetMultiplayerState(); + if (!multiplayer_state) return; + + if (multiplayer_state->IsClientRoomVisible()) { + chat_room_widget->setEnabled(false); + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Chat available in main window.")); + return; + } + + chat_room_widget->setEnabled(true); + auto& room_network = multiplayer_state->GetRoomNetwork(); + room_member = room_network.GetRoomMember().lock(); + + if (room_member) { + chat_room_widget->Initialize(&room_network); + } else { + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Not connected to a room.")); + } +} + +void MultiplayerRoomOverlay::DisconnectFromRoom() { + chat_room_widget->Clear(); + room_member.reset(); + multiplayer_state = nullptr; + players_online_label->setText(QString::fromUtf8("Players Online: 0")); +} + +void MultiplayerRoomOverlay::UpdateRoomData() { + if (!multiplayer_state) { + ConnectToRoom(); + return; + } + + if (multiplayer_state->IsClientRoomVisible()) { + if (chat_room_widget->isEnabled()) { + chat_room_widget->setEnabled(false); + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Chat available in main window.")); + } + } else { + if (!chat_room_widget->isEnabled()) { + ConnectToRoom(); + } + } + + if (room_member && room_member->GetState() >= Network::RoomMember::State::Joined) { + const auto& members = room_member->GetMemberInformation(); + QString label_text = QString::fromStdString("Players Online: %1").arg(members.size()); + players_online_label->setText(label_text); + if (chat_room_widget->isEnabled()) { + chat_room_widget->SetPlayerList(members); + } + } else { + players_online_label->setText(QString::fromUtf8("Players Online: 0")); + if (!room_member && !multiplayer_state->IsClientRoomVisible()) { + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Not connected to a room.")); + ConnectToRoom(); + } + } +} + +void MultiplayerRoomOverlay::UpdatePosition() { + if (!main_window) return; + if (!has_been_moved) { + QPoint main_window_pos = main_window->mapToGlobal(QPoint(0, 0)); + move(main_window_pos.x() + main_window->width() - this->width() - 10, main_window_pos.y() + 10); + } +}