[Room][UserList] Introduce style delegate (#6981)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run

* [Room] Additionally show a tab for friends and ignored users instead of just all online users.

Took 21 minutes


Took 12 minutes

* [Room][UserList] Introduce style delegate for user list
- Allow users to set a card name and parameters as their background banner
- Allow mods to white/blacklist cards
- Allow toggling back to the old display style

Took 7 minutes

Took 28 seconds

Took 2 minutes

Took 2 minutes

* Right checkstate.

Took 14 minutes


Took 2 minutes

* Utility for test.

Took 9 minutes

Took 8 seconds

Took 2 seconds

* Lint.

Took 10 minutes

* Algorithm for sql schema migration

Took 13 minutes

* Use {prefix}, bound card name, return errors.

Took 27 seconds

* Convert queue to while loop.

Took 19 seconds

* Hover popup.

Took 36 minutes

Took 1 minute

* More granular signals, popup for user info.

Took 25 minutes

Took 8 seconds

Took 16 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-06-26 20:52:24 -04:00 committed by GitHub
parent c9ebdb451f
commit 2914874720
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 3387 additions and 83 deletions

View file

@ -0,0 +1,181 @@
#ifndef COCKATRICE_USER_INFO_POPUP_H
#define COCKATRICE_USER_INFO_POPUP_H
#include "../../interface/widgets/server/game_type_map.h"
#include "user_list_painter.h"
#include <QFrame>
#include <QListView>
#include <QMap>
#include <QPixmap>
#include <QStandardItemModel>
#include <libcockatrice/network/server/remote/user_level.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
class AbstractClient;
class QLabel;
class QPushButton;
class TabSupervisor;
// ── Roles ─────────────────────────────────────────────────────────────────────
namespace PopupRoles
{
constexpr int GameData = Qt::UserRole + 10;
}
// ── Header widget ─────────────────────────────────────────────────────────────
/**
* @class UserInfoHeaderWidget
* @brief Paints the enlarged banner card art + circular avatar section at the
* top of the UserInfoPopup.
*
* Layout mirrors UserListPainter but at a larger scale: the card art fills the
* full width as a semi-transparent background, a bottom gradient ensures the
* avatar and username text remain legible, and the status ring colour matches
* the UserListPainter convention.
*/
class UserInfoHeaderWidget : public QWidget
{
Q_OBJECT
static constexpr int HeaderHeight = 130;
static constexpr int AvatarSize = 68;
static constexpr int AvatarPawnSize = 46;
static constexpr int LeftPad = 14;
static constexpr int AvatarToTextGap = 10;
public:
explicit UserInfoHeaderWidget(QWidget *parent = nullptr);
void setUserData(const ServerInfo_User &user,
bool online,
const QPixmap &avatar,
const QPixmap &cardArt,
const CardArtParams &params);
protected:
void paintEvent(QPaintEvent *e) override;
private:
ServerInfo_User m_user;
bool m_online = false;
QPixmap m_avatar;
QPixmap m_cardArt;
CardArtParams m_params;
};
// ── Main popup ────────────────────────────────────────────────────────────────
/**
* @class UserInfoPopup
* @brief Floating panel showing an enlarged user card, quick action buttons,
* and a live scrollable games list.
*
* Lifecycle (mirrors DeckEditorDeckDockWidget):
* - showForUser() populate, position externally, call show()
* - mouseEnteredPopup / mouseLeftPopup caller manages hide timer
* - closeRequested() emitted by the internal close button
*
* The popup is a Qt::Tool frameless child so windowOpacity animations and
* move() in screen coordinates work identically to CardInfoPictureEnlargedWidget.
*
* Action signals map 1-to-1 to UserContextMenu::exec*() methods so all action
* logic stays in one place.
*/
class UserInfoPopup : public QFrame
{
Q_OBJECT
static constexpr int PopupWidth = 316;
public:
explicit UserInfoPopup(TabSupervisor *tabSupervisor,
AbstractClient *client,
const QMap<QString, QPixmap> *avatarCache,
const QMap<QString, QPixmap> *cardArtCache,
const QMap<QString, CardArtParams> *cardArtParamsMap,
QWidget *parent);
/**
* Populate the popup for @p userName and kick off a game list fetch.
* Call show() / move() externally after this.
*/
void
showForUser(const QString &userName, const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored);
void fetchGames();
[[nodiscard]] QString currentUser() const
{
return m_currentUser;
}
/** Called when buddy/ignore status changes externally while popup is open. */
void updateActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored);
signals:
void mouseEnteredPopup();
void mouseLeftPopup();
void closeRequested();
/** Emitted when the user requests joining or spectating a game in the list. */
void joinGameRequested(int gameId, int roomId, bool asSpectator);
// ── Action signals — connect to UserContextMenu::exec*() ──────────────────
void chatRequested(const QString &userName);
void detailsRequested(const QString &userName);
void showGamesRequested(const QString &userName);
void addBuddyRequested(const QString &userName);
void removeBuddyRequested(const QString &userName);
void addIgnoreRequested(const QString &userName);
void removeIgnoreRequested(const QString &userName);
void banRequested(const QString &userName);
void warnRequested(const QString &userName);
void banHistoryRequested(const QString &userName);
void warnHistoryRequested(const QString &userName);
void adminNotesRequested(const QString &userName);
void promoteToModRequested(const QString &userName);
void demoteFromModRequested(const QString &userName);
void promoteToJudgeRequested(const QString &userName);
void demoteFromJudgeRequested(const QString &userName);
protected:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent *e) override;
#else
void enterEvent(QEvent *e) override;
#endif
void leaveEvent(QEvent *e) override;
private slots:
void refreshGames();
void onGamesReceived(const Response &r, const QString &forUser);
void onGamesContextMenu(const QPoint &pos);
private:
void buildUi();
void rebuildActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored);
TabSupervisor *m_ts;
AbstractClient *m_client;
const QMap<QString, QPixmap> *m_avatarCache;
const QMap<QString, QPixmap> *m_cardArtCache;
const QMap<QString, CardArtParams> *m_cardArtParamsMap;
QString m_currentUser;
ServerInfo_User m_currentUserInfo;
bool m_currentOnline = false;
UserInfoHeaderWidget *m_header;
QWidget *m_actionArea; ///< rebuilt per user
QListView *m_gamesView;
QStandardItemModel *m_gamesModel;
QLabel *m_gamesStatus;
QPushButton *m_closeBtn;
QPushButton *m_refreshBtn;
};
#endif // COCKATRICE_USER_INFO_POPUP_H