From 144847611ec06476dd12d6d6b8b5a8c1fbffff54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Tue, 13 Oct 2026 22:56:03 +0200 Subject: [PATCH] [Client] Flat moderation headers and detail tab shape --- cockatrice/CMakeLists.txt | 2 + .../widgets/tabs/moderation_header_style.cpp | 56 +++++++++++++++++++ .../widgets/tabs/moderation_header_style.h | 25 +++++++++ .../widgets/tabs/moderation_tab_style.cpp | 46 +++++++++++++++ .../widgets/tabs/moderation_tab_style.h | 25 +++++++++ .../src/interface/widgets/tabs/tab_alts.cpp | 2 + .../src/interface/widgets/tabs/tab_report.cpp | 4 ++ 7 files changed, 160 insertions(+) create mode 100644 cockatrice/src/interface/widgets/tabs/moderation_header_style.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/moderation_header_style.h create mode 100644 cockatrice/src/interface/widgets/tabs/moderation_tab_style.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/moderation_tab_style.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index fc593058a..12fd31765 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -381,6 +381,8 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp src/interface/widgets/tabs/alt_table_delegate.cpp + src/interface/widgets/tabs/moderation_header_style.cpp + src/interface/widgets/tabs/moderation_tab_style.cpp src/interface/widgets/tabs/report_status_delegate.cpp src/interface/widgets/tabs/tab.cpp src/interface/widgets/tabs/tab_account.cpp diff --git a/cockatrice/src/interface/widgets/tabs/moderation_header_style.cpp b/cockatrice/src/interface/widgets/tabs/moderation_header_style.cpp new file mode 100644 index 000000000..1b9aadb6f --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/moderation_header_style.cpp @@ -0,0 +1,56 @@ +#include "moderation_header_style.h" + +#include +#include +#include +#include + +namespace +{ +constexpr qreal HOVER_WASH_ALPHA = 0.12; +} // namespace + +ModerationHeaderStyle::ModerationHeaderStyle(QStyle *base) : QProxyStyle(base) +{ +} + +void ModerationHeaderStyle::drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget) const +{ + if (element != CE_Header || option == nullptr || painter == nullptr) { + QProxyStyle::drawControl(element, option, painter, widget); + return; + } + + const auto *headerOpt = qstyleoption_cast(option); + if (headerOpt == nullptr || !headerOpt->icon.isNull()) { + QProxyStyle::drawControl(element, option, painter, widget); + return; + } + + painter->save(); + painter->fillRect(headerOpt->rect, headerOpt->palette.window()); + + const bool enabled = (headerOpt->state & State_Enabled) != 0; + if (enabled && (headerOpt->state & State_MouseOver) != 0) { + QColor wash = headerOpt->palette.color(QPalette::Highlight); + wash.setAlphaF(HOVER_WASH_ALPHA); + painter->fillRect(headerOpt->rect, wash); + } + + painter->setPen(headerOpt->palette.color(QPalette::Mid)); + painter->drawLine(headerOpt->rect.bottomLeft(), headerOpt->rect.bottomRight()); + painter->restore(); + + QRect textRect = headerOpt->rect.adjusted(4, 0, -4, 0); + const QString elided = headerOpt->fontMetrics.elidedText(headerOpt->text, Qt::ElideRight, textRect.width()); + QStyle *style = widget != nullptr ? widget->style() : QApplication::style(); + style->drawItemText(painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, headerOpt->palette, enabled, elided, + QPalette::WindowText); + + if (headerOpt->sortIndicator != QStyleOptionHeader::None) { + QProxyStyle::drawPrimitive(PE_IndicatorHeaderArrow, option, painter, widget); + } +} diff --git a/cockatrice/src/interface/widgets/tabs/moderation_header_style.h b/cockatrice/src/interface/widgets/tabs/moderation_header_style.h new file mode 100644 index 000000000..c9b95c77c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/moderation_header_style.h @@ -0,0 +1,25 @@ +#ifndef MODERATION_HEADER_STYLE_H +#define MODERATION_HEADER_STYLE_H + +#include +#include + +/** + * Flat header sections for the moderation tables (report queue, user alts). + * Replaces the native gradient header with the palette Window background, a + * 1px Mid bottom rule, and WindowText labels, so headers follow the active + * theme. Sort indicators are still drawn by the base style. Headers with + * icons fall back to the base style untouched. + */ +class ModerationHeaderStyle : public QProxyStyle +{ + Q_OBJECT +public: + explicit ModerationHeaderStyle(QStyle *base = nullptr); + void drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget = nullptr) const override; +}; + +#endif // MODERATION_HEADER_STYLE_H diff --git a/cockatrice/src/interface/widgets/tabs/moderation_tab_style.cpp b/cockatrice/src/interface/widgets/tabs/moderation_tab_style.cpp new file mode 100644 index 000000000..0f5cb6444 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/moderation_tab_style.cpp @@ -0,0 +1,46 @@ +#include "moderation_tab_style.h" + +#include +#include + +namespace +{ +constexpr int UNDERLINE_HEIGHT = 2; +constexpr qreal HOVER_WASH_ALPHA = 0.08; +} // namespace + +ModerationTabStyle::ModerationTabStyle(QStyle *base) : QProxyStyle(base) +{ +} + +void ModerationTabStyle::drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget) const +{ + if (element != CE_TabBarTabShape || option == nullptr || painter == nullptr) { + QProxyStyle::drawControl(element, option, painter, widget); + return; + } + + const auto *tabOpt = qstyleoption_cast(option); + if (tabOpt == nullptr) { + QProxyStyle::drawControl(element, option, painter, widget); + return; + } + + painter->save(); + painter->fillRect(tabOpt->rect, tabOpt->palette.window()); + + const bool enabled = (tabOpt->state & State_Enabled) != 0; + if ((tabOpt->state & State_Selected) != 0) { + const QRect underline(tabOpt->rect.left(), tabOpt->rect.bottom() - UNDERLINE_HEIGHT + 1, tabOpt->rect.width(), + UNDERLINE_HEIGHT); + painter->fillRect(underline, tabOpt->palette.color(QPalette::Highlight)); + } else if (enabled && (tabOpt->state & State_MouseOver) != 0) { + QColor wash = tabOpt->palette.color(QPalette::Highlight); + wash.setAlphaF(HOVER_WASH_ALPHA); + painter->fillRect(tabOpt->rect, wash); + } + painter->restore(); +} diff --git a/cockatrice/src/interface/widgets/tabs/moderation_tab_style.h b/cockatrice/src/interface/widgets/tabs/moderation_tab_style.h new file mode 100644 index 000000000..57f61e5ed --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/moderation_tab_style.h @@ -0,0 +1,25 @@ +#ifndef MODERATION_TAB_STYLE_H +#define MODERATION_TAB_STYLE_H + +#include +#include + +/** + * Flat tab shapes for the moderation detail tabs (report queue detailTabs). + * Replaces the native tab frame with the palette Window background plus a + * 2px Highlight underline on the selected tab and a faint Highlight wash on + * hover. Labels (text, elide, focus) are left to the base style, so keyboard + * focus stays distinguishable. + */ +class ModerationTabStyle : public QProxyStyle +{ + Q_OBJECT +public: + explicit ModerationTabStyle(QStyle *base = nullptr); + void drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget = nullptr) const override; +}; + +#endif // MODERATION_TAB_STYLE_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_alts.cpp b/cockatrice/src/interface/widgets/tabs/tab_alts.cpp index d2cec3855..a0fc46295 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_alts.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_alts.cpp @@ -6,6 +6,7 @@ #include "alt_table_delegate.h" #include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response_user_alts.pb.h" +#include "moderation_header_style.h" #include #include @@ -78,6 +79,7 @@ void configureAltTable(QTableWidget *table) table->setAlternatingRowColors(true); table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); table->horizontalHeader()->setStretchLastSection(true); + table->horizontalHeader()->setStyle(new ModerationHeaderStyle(table->horizontalHeader()->style())); } } // namespace diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 0dda1ed66..e4ad4acbc 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -4,6 +4,8 @@ #include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" +#include "moderation_header_style.h" +#include "moderation_tab_style.h" #include "report_status_delegate.h" #include "tab_supervisor.h" @@ -122,6 +124,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T table->horizontalHeader()->setSectionResizeMode(COL_TIME, QHeaderView::ResizeToContents); table->horizontalHeader()->setSectionResizeMode(COL_REPORTED, QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(COL_REPORTER, QHeaderView::ResizeToContents); + table->horizontalHeader()->setStyle(new ModerationHeaderStyle(table->horizontalHeader()->style())); connect(table, &QTableWidget::itemSelectionChanged, this, &TabReport::onSelectionChanged); table->setContextMenuPolicy(Qt::CustomContextMenu); connect(table, &QTableWidget::customContextMenuRequested, this, &TabReport::showTableContextMenu); @@ -238,6 +241,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T }); detailTabs = new QTabWidget; + detailTabs->tabBar()->setStyle(new ModerationTabStyle(detailTabs->tabBar()->style())); detailTabs->addTab(descPage, QString()); detailTabs->addTab(chatPage, QString()); detailTabs->addTab(commentsPage, QString());