mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-01-26 12:43:29 +00:00
feat(multiplayer): More QoL Improvements
Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QScreen>
|
||||
@@ -23,24 +24,25 @@ MultiplayerRoomOverlay::MultiplayerRoomOverlay(QWidget* parent)
|
||||
|
||||
main_window = qobject_cast<GMainWindow*>(parent->window());
|
||||
|
||||
// Switched to Qt::Tool to allow keyboard focus for typing in chat
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
// Set smaller sizes for Steam Deck
|
||||
setMinimumSize(240, 180);
|
||||
resize(260, 220);
|
||||
|
||||
main_layout = new QGridLayout(this);
|
||||
main_layout->setContentsMargins(padding, padding, 0, 0);
|
||||
main_layout->setSpacing(6);
|
||||
main_layout->setContentsMargins(padding, padding, padding, padding);
|
||||
main_layout->setSpacing(8);
|
||||
|
||||
players_online_label = new QLabel(this);
|
||||
|
||||
QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
|
||||
shadow->setBlurRadius(6);
|
||||
shadow->setColor(Qt::black);
|
||||
shadow->setOffset(0, 0);
|
||||
players_online_label->setGraphicsEffect(shadow);
|
||||
|
||||
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->setText(QString::fromUtf8("Players Online: 0"));
|
||||
players_online_label->setFont(QFont(QString::fromUtf8("Segoe UI"), 12, QFont::Bold));
|
||||
players_online_label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
||||
|
||||
size_grip->setFixedSize(16, 16);
|
||||
@@ -66,21 +68,19 @@ MultiplayerRoomOverlay::MultiplayerRoomOverlay(QWidget* parent)
|
||||
|
||||
if (main_window) {
|
||||
connect(main_window, &GMainWindow::themeChanged, this, &MultiplayerRoomOverlay::UpdateTheme);
|
||||
connect(main_window, &GMainWindow::EmulationStarting, this, &MultiplayerRoomOverlay::OnEmulationStarting);
|
||||
connect(main_window, &GMainWindow::EmulationStopping, this, &MultiplayerRoomOverlay::OnEmulationStopping);
|
||||
}
|
||||
UpdateTheme();
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
setMinimumSize(320, 260);
|
||||
resize(600, 520);
|
||||
|
||||
players_online_label->setFont(QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold));
|
||||
|
||||
this->padding = 12;
|
||||
main_layout->setContentsMargins(padding, padding, padding, padding);
|
||||
setMinimumSize(450, 350);
|
||||
resize(700, 550);
|
||||
this->padding = 15;
|
||||
} else {
|
||||
setMinimumSize(280, 220);
|
||||
resize(320, 280);
|
||||
setMinimumSize(360, 260);
|
||||
resize(420, 300);
|
||||
}
|
||||
|
||||
UpdatePosition();
|
||||
@@ -91,17 +91,30 @@ MultiplayerRoomOverlay::~MultiplayerRoomOverlay() {
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::OnEmulationStarting() {
|
||||
// The connection is now managed by the overlay's visibility.
|
||||
// Force a UI refresh immediately when a game starts
|
||||
UpdateRoomData();
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::OnEmulationStopping() {
|
||||
// The connection should persist even when emulation stops.
|
||||
update_timer.stop();
|
||||
|
||||
if (room_member && room_member->IsConnected()) {
|
||||
// Only send if the state is stable
|
||||
room_member->SendGameInfo({});
|
||||
}
|
||||
|
||||
// Clear the UI text but don't force a full room data poll yet
|
||||
players_online_label->setText(tr("Emulation Stopped."));
|
||||
|
||||
// Resume polling after 1 second once the LDN service has safely detached
|
||||
QTimer::singleShot(1000, this, [this] {
|
||||
if (is_visible) update_timer.start(500);
|
||||
});
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::SetVisible(bool visible) {
|
||||
if (is_visible == visible) return;
|
||||
is_visible = visible;
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
ConnectToRoom();
|
||||
@@ -117,21 +130,36 @@ void MultiplayerRoomOverlay::paintEvent(QPaintEvent* event) {
|
||||
Q_UNUSED(event)
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
QRect background_rect = rect();
|
||||
|
||||
// Move the top of the box down so the text floats above it
|
||||
int label_area_height = players_online_label->height() + main_layout->spacing() + padding;
|
||||
background_rect.setTop(label_area_height);
|
||||
|
||||
QPainterPath background_path;
|
||||
background_path.addRoundedRect(rect(), corner_radius, corner_radius);
|
||||
background_path.addRoundedRect(background_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); }
|
||||
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);
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
if (UISettings::IsGamescope()) {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
@@ -171,36 +199,27 @@ 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("In order to use chat functionality in the Overlay, please close the Multiplayer Room Window."));
|
||||
chat_room_widget->AppendStatusMessage(tr("Please close the Multiplayer Room Window to use the Overlay."));
|
||||
return;
|
||||
}
|
||||
|
||||
chat_room_widget->setEnabled(true);
|
||||
auto& room_network = multiplayer_state->GetRoomNetwork();
|
||||
room_member = room_network.GetRoomMember().lock();
|
||||
|
||||
if (room_member) {
|
||||
if (!is_chat_initialized) {
|
||||
chat_room_widget->Initialize(&room_network);
|
||||
is_chat_initialized = true;
|
||||
}
|
||||
} else {
|
||||
chat_room_widget->Clear();
|
||||
chat_room_widget->AppendStatusMessage(tr("Not connected to a room."));
|
||||
ClearUI();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::DisconnectFromRoom() {
|
||||
// Tell the chat widget to disconnect its signals *before*
|
||||
// we reset our own state.
|
||||
if (is_chat_initialized && chat_room_widget) {
|
||||
chat_room_widget->Shutdown();
|
||||
}
|
||||
|
||||
if (is_chat_initialized && chat_room_widget) chat_room_widget->Shutdown();
|
||||
ClearUI();
|
||||
room_member.reset();
|
||||
multiplayer_state = nullptr;
|
||||
@@ -208,70 +227,90 @@ void MultiplayerRoomOverlay::DisconnectFromRoom() {
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::ClearUI() {
|
||||
players_online_label->setText(QString::fromUtf8("Players Online: 0"));
|
||||
players_online_label->setText(tr("Not connected to a room."));
|
||||
chat_room_widget->Clear();
|
||||
chat_room_widget->AppendStatusMessage(tr("Not connected to a room."));
|
||||
chat_room_widget->SetPlayerList({});
|
||||
}
|
||||
|
||||
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("In order to use chat functionality in the Overlay, please close the Multiplayer Room Window."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chat_room_widget->isEnabled()) {
|
||||
ConnectToRoom();
|
||||
}
|
||||
if (!multiplayer_state) { ConnectToRoom(); return; }
|
||||
if (multiplayer_state->IsClientRoomVisible()) { chat_room_widget->setEnabled(false); return; }
|
||||
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: <span style='color: #4CAF50;'>%1</span>").arg(members.size());
|
||||
players_online_label->setText(label_text);
|
||||
|
||||
if (chat_room_widget->isEnabled()) {
|
||||
chat_room_widget->SetPlayerList(members);
|
||||
AnnounceMultiplayerRoom::GameInfo local_game_info;
|
||||
std::string my_nick = room_member->GetNickname();
|
||||
for (const auto& m : members) {
|
||||
if (m.nickname == my_nick) {
|
||||
local_game_info = m.game_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ClearUI();
|
||||
room_member.reset();
|
||||
|
||||
// Ensure we don't think we are emulating if the status is "Not playing a game"
|
||||
bool is_emulating = !local_game_info.name.empty() &&
|
||||
local_game_info.name != tr("Not playing a game").toStdString();
|
||||
|
||||
int point_size = UISettings::IsGamescope() ? 11 : 10;
|
||||
if (this->width() < 340) point_size = 10;
|
||||
|
||||
QFont font = players_online_label->font();
|
||||
font.setPointSize(point_size);
|
||||
players_online_label->setFont(font);
|
||||
|
||||
QString label_text;
|
||||
if (!is_emulating) {
|
||||
players_online_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
label_text = tr("<b>Players In Room: <span style='color: #00FF00;'>%1</span></b>").arg(members.size());
|
||||
} else {
|
||||
players_online_label->setAlignment(Qt::AlignCenter);
|
||||
int g = 0, d = 0, o = 0;
|
||||
for (const auto& m : members) {
|
||||
bool m_playing = !m.game_info.name.empty() &&
|
||||
m.game_info.name != tr("Not playing a game").toStdString();
|
||||
|
||||
if (m_playing && m.game_info.name == local_game_info.name) {
|
||||
if (m.game_info.version == local_game_info.version) g++; else d++;
|
||||
} else {
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList parts;
|
||||
if (g > 0) parts << tr("<b>In-Game: <span style='color: #00FF00;'>%1</span></b>").arg(g);
|
||||
if (d > 0) parts << tr("<b>Different Update: <span style='color: #FFD700;'>%1</span></b>").arg(d);
|
||||
if (o > 0) parts << tr("<b>Other: <span style='color: #E0E0E0;'>%1</span></b>").arg(o);
|
||||
|
||||
QString sep = QStringLiteral(" • ");
|
||||
if (this->width() < 400) sep = QStringLiteral(" • ");
|
||||
|
||||
label_text = parts.join(sep);
|
||||
}
|
||||
players_online_label->setText(label_text);
|
||||
if (chat_room_widget->isEnabled()) chat_room_widget->SetPlayerList(members);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
QPoint win_pos = main_window->mapToGlobal(QPoint(0, 0));
|
||||
move(win_pos.x() + main_window->width() - width() - 15, win_pos.y() + 15);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::UpdateTheme() {
|
||||
if (UISettings::IsDarkTheme()) {
|
||||
// Dark Theme Colors
|
||||
background_color = QColor(20, 20, 20, 180); // 180 alpha for transparency
|
||||
border_color = QColor(60, 60, 60, 120);
|
||||
players_online_label->setStyleSheet(QStringLiteral("color: #E0E0E0;"));
|
||||
background_color = QColor(25, 25, 25, 225);
|
||||
border_color = QColor(255, 255, 255, 40);
|
||||
players_online_label->setStyleSheet(QStringLiteral("color: #FFFFFF;"));
|
||||
} else {
|
||||
// Light Theme Colors
|
||||
background_color = QColor(245, 245, 245, 200); // 200 alpha for transparency
|
||||
border_color = QColor(200, 200, 200, 120);
|
||||
players_online_label->setStyleSheet(QStringLiteral("color: #141414;"));
|
||||
background_color = QColor(245, 245, 245, 235);
|
||||
border_color = QColor(0, 0, 0, 50);
|
||||
players_online_label->setStyleSheet(QStringLiteral("color: #111111;"));
|
||||
}
|
||||
|
||||
// The chat widget is a separate custom widget, so we need to tell it to update its theme too.
|
||||
if (chat_room_widget) {
|
||||
chat_room_widget->UpdateTheme();
|
||||
}
|
||||
|
||||
update(); // Force a repaint of the overlay itself
|
||||
if (chat_room_widget) chat_room_widget->UpdateTheme();
|
||||
update();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user