example/gui/theme/ColorSchemePage.cpp¶
Color Scheme page - Implementation. More...
Namespaces¶
| Name |
|---|
| cf |
| cf::ui |
| cf::ui::gallery |
Detailed Description¶
Color Scheme page - Implementation.
Author: CFDesktop Team
Version: 0.1
Date: 2026-02-28
Source code¶
#include "ColorSchemePage.h"
#include "ToastWidget.h"
#include "ui/core/material/cfmaterial_scheme.h"
#include "ui/core/material/material_factory.hpp"
#include "ui/core/theme.h"
#include "ui/core/token/material_scheme/cfmaterial_token_literals.h"
#include <QClipboard>
#include <QFont>
#include <QGuiApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPropertyAnimation>
#include <QTimer>
// Global namespace alias for token literals
namespace token_literals = ::cf::ui::core::token::literals;
namespace cf::ui::gallery {
// =============================================================================
// Static Token Lists
// =============================================================================
const QStringList ColorSchemePage::PRIMARY_TOKENS = {
token_literals::PRIMARY, token_literals::ON_PRIMARY, token_literals::PRIMARY_CONTAINER,
token_literals::ON_PRIMARY_CONTAINER};
const QStringList ColorSchemePage::SECONDARY_TOKENS = {
token_literals::SECONDARY, token_literals::ON_SECONDARY, token_literals::SECONDARY_CONTAINER,
token_literals::ON_SECONDARY_CONTAINER};
const QStringList ColorSchemePage::TERTIARY_TOKENS = {
token_literals::TERTIARY, token_literals::ON_TERTIARY, token_literals::TERTIARY_CONTAINER,
token_literals::ON_TERTIARY_CONTAINER};
const QStringList ColorSchemePage::ERROR_TOKENS = {token_literals::ERROR, token_literals::ON_ERROR,
token_literals::ERROR_CONTAINER,
token_literals::ON_ERROR_CONTAINER};
const QStringList ColorSchemePage::SURFACE_TOKENS = {
token_literals::BACKGROUND, token_literals::ON_BACKGROUND, token_literals::SURFACE,
token_literals::ON_SURFACE, token_literals::SURFACE_VARIANT, token_literals::ON_SURFACE_VARIANT,
token_literals::OUTLINE, token_literals::OUTLINE_VARIANT};
const QStringList ColorSchemePage::UTILITY_TOKENS = {
token_literals::SHADOW, token_literals::SCRIM, token_literals::INVERSE_SURFACE,
token_literals::INVERSE_ON_SURFACE, token_literals::INVERSE_PRIMARY};
// =============================================================================
// ColorCardWidget Implementation
// =============================================================================
ColorCardWidget::ColorCardWidget(const QString& tokenName, const QColor& color,
const QString& contrastInfo, QWidget* parent)
: QWidget(parent), tokenName_(tokenName), color_(color), contrastInfo_(contrastInfo) {
hexValue_ = color.name().toUpper();
setMinimumSize(140, 160);
setMaximumSize(180, 200);
setCursor(Qt::PointingHandCursor);
}
void ColorCardWidget::updateColor(const QColor& color, const QString& contrastInfo) {
color_ = color;
hexValue_ = color.name().toUpper();
contrastInfo_ = contrastInfo;
update();
}
void ColorCardWidget::paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QRectF r = rect().adjusted(2, 2, -2, -2);
qreal radius = 12;
// Card background with elevation
QPainterPath path;
path.addRoundedRect(r, radius, radius);
// Shadow for elevation effect
if (isHovered_) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 20));
painter.drawPath(path.translated(0, 2));
}
painter.setPen(QPen(QColor(200, 200, 200), 1));
painter.setBrush(QColor(250, 250, 250));
painter.drawPath(path);
// Color swatch
qreal swatchSize = std::min(r.width(), r.height() * 0.35);
QRectF swatchRect(r.center().x() - swatchSize / 2, r.top() + 12, swatchSize, swatchSize);
QPainterPath swatchPath;
swatchPath.addRoundedRect(swatchRect, 8, 8);
painter.setPen(Qt::NoPen);
painter.setBrush(color_);
painter.drawPath(swatchPath);
// Token name
QFont nameFont("Segoe UI", 8, QFont::Medium);
painter.setFont(nameFont);
painter.setPen(QColor(60, 60, 60));
QRectF nameRect = r.adjusted(8, swatchRect.bottom() + 8, -8, 0);
painter.drawText(nameRect, Qt::AlignTop | Qt::AlignHCenter, tokenName_);
// Separator line
qreal lineY = nameRect.top() + QFontMetrics(nameFont).height() + 6;
QPen linePen(QColor(200, 200, 200), 1);
painter.setPen(linePen);
painter.drawLine(QPointF(r.left() + 16, lineY), QPointF(r.right() - 16, lineY));
// HEX value
QFont hexFont("Consolas", 9);
painter.setFont(hexFont);
painter.setPen(QColor(40, 40, 40));
QRectF hexRect = r.adjusted(8, lineY + 4, -8, 0);
painter.drawText(hexRect, Qt::AlignTop | Qt::AlignHCenter, hexValue_);
// Contrast info with color coding
QFont contrastFont("Segoe UI", 7);
painter.setFont(contrastFont);
// Parse contrast value
float contrast = contrastInfo_.split(": ").last().toFloat();
QColor contrastColor;
if (contrast < 3.0f) {
contrastColor = QColor(220, 50, 50);
} else if (contrast < 4.5f) {
contrastColor = QColor(220, 150, 50);
} else {
contrastColor = QColor(50, 180, 80);
}
painter.setPen(contrastColor);
QRectF contrastRect = r.adjusted(8, hexRect.top() + QFontMetrics(hexFont).height() + 2, -8, -8);
painter.drawText(contrastRect, Qt::AlignTop | Qt::AlignHCenter, contrastInfo_);
// Click hint on hover
if (isHovered_) {
QFont hintFont("Segoe UI", 7);
painter.setFont(hintFont);
painter.setPen(QColor(100, 100, 100));
painter.drawText(r.adjusted(8, 0, -8, -4), Qt::AlignBottom | Qt::AlignHCenter,
"Click to copy");
}
}
void ColorCardWidget::enterEvent(QEnterEvent*) {
isHovered_ = true;
update();
}
void ColorCardWidget::leaveEvent(QEvent*) {
isHovered_ = false;
update();
}
void ColorCardWidget::mousePressEvent(QMouseEvent*) {
emit clicked(hexValue_);
}
// =============================================================================
// ColorSchemePage Implementation
// =============================================================================
ColorSchemePage::ColorSchemePage(QWidget* parent) : ThemePageWidget(parent) {
// Default card colors (light theme)
cardBgColor_ = QColor(250, 250, 250);
cardBorderColor_ = QColor(220, 220, 220);
setupUI();
// Note: createColorGroups will be called after theme is set
}
void ColorSchemePage::setupUI() {
auto* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Scroll area for color cards
scrollArea_ = new QScrollArea(this);
scrollArea_->setWidgetResizable(true);
scrollArea_->setFrameShape(QFrame::NoFrame);
scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollContent_ = new QWidget();
scrollLayout_ = new QVBoxLayout(scrollContent_);
scrollLayout_->setSpacing(24);
scrollLayout_->setContentsMargins(24, 24, 24, 24);
colorGridLayout_ = new QGridLayout();
colorGridLayout_->setSpacing(12);
colorGridLayout_->setContentsMargins(0, 0, 0, 0);
scrollLayout_->addLayout(colorGridLayout_);
scrollLayout_->addStretch();
scrollArea_->setWidget(scrollContent_);
mainLayout->addWidget(scrollArea_);
// Toast
toast_ = new ToastWidget("", this);
toast_->hide();
}
void ColorSchemePage::createColorGroups() {
int row = 0;
createColorGroup("Primary Colors 主色", colorGridLayout_, row, PRIMARY_TOKENS);
createColorGroup("Secondary Colors 副色", colorGridLayout_, row, SECONDARY_TOKENS);
createColorGroup("Tertiary Colors 第三色", colorGridLayout_, row, TERTIARY_TOKENS);
createColorGroup("Error Colors 错误色", colorGridLayout_, row, ERROR_TOKENS);
createColorGroup("Surface Colors 表面色", colorGridLayout_, row, SURFACE_TOKENS);
createColorGroup("Utility Colors 工具色", colorGridLayout_, row, UTILITY_TOKENS);
}
void ColorSchemePage::createColorGroup(const QString& title, QGridLayout* layout, int& row,
const QStringList& tokens) {
// Get color scheme from theme
if (!theme_)
return;
auto& colorScheme = const_cast<cf::ui::core::MaterialColorScheme&>(
static_cast<const cf::ui::core::MaterialColorScheme&>(theme_->color_scheme()));
// Group title
QLabel* titleLabel = new QLabel(title, scrollContent_);
QFont titleFont("Segoe UI", 13, QFont::Bold);
titleLabel->setFont(titleFont);
titleLabel->setStyleSheet("QLabel { color: #49454F; padding: 4px 0px; }");
layout->addWidget(titleLabel, row++, 0, 1, -1, Qt::AlignLeft);
// Calculate column count
int col = 0;
int maxCols = 4;
layout->setRowStretch(row, 0);
for (const QString& token : tokens) {
QColor color = colorScheme.queryColor(token.toStdString().c_str());
// Calculate contrast
QString pairToken = getContrastPair(token);
QColor pairColor = colorScheme.queryColor(pairToken.toStdString().c_str());
float contrast = calculateContrastRatio(color, pairColor);
QString contrastInfo = QString("⚡ %1").arg(contrast, 0, 'f', 2);
auto* card = new ColorCardWidget(token, color, contrastInfo, scrollContent_);
layout->addWidget(card, row, col++);
ColorCardInfo info{card, token};
colorCards_.append(info);
connect(card, &ColorCardWidget::clicked, this, &ColorSchemePage::onColorCardClicked);
if (col >= maxCols) {
col = 0;
row++;
}
}
if (col > 0) {
row++;
}
// Add spacing after group
row++;
}
QString ColorSchemePage::getContrastPair(const QString& token) const {
static const QMap<QString, QString> pairs = {
{"md.primary", "md.onPrimary"},
{"md.onPrimary", "md.primary"},
{"md.primaryContainer", "md.onPrimaryContainer"},
{"md.onPrimaryContainer", "md.primaryContainer"},
{"md.secondary", "md.onSecondary"},
{"md.onSecondary", "md.secondary"},
{"md.secondaryContainer", "md.onSecondaryContainer"},
{"md.onSecondaryContainer", "md.secondaryContainer"},
{"md.tertiary", "md.onTertiary"},
{"md.onTertiary", "md.tertiary"},
{"md.tertiaryContainer", "md.onTertiaryContainer"},
{"md.onTertiaryContainer", "md.tertiaryContainer"},
{"md.error", "md.onError"},
{"md.onError", "md.error"},
{"md.errorContainer", "md.onErrorContainer"},
{"md.onErrorContainer", "md.errorContainer"},
{"md.background", "md.onBackground"},
{"md.onBackground", "md.background"},
{"md.surface", "md.onSurface"},
{"md.onSurface", "md.surface"},
{"md.surfaceVariant", "md.onSurfaceVariant"},
{"md.onSurfaceVariant", "md.surfaceVariant"},
{"md.outline", "md.surface"},
{"md.outlineVariant", "md.surface"},
{"md.shadow", "md.surface"},
{"md.scrim", "md.surface"},
{"md.inverseSurface", "md.inverseOnSurface"},
{"md.inverseOnSurface", "md.inverseSurface"},
{"md.inversePrimary", "md.surface"}};
return pairs.value(token, "md.surface");
}
float ColorSchemePage::calculateContrastRatio(const QColor& fg, const QColor& bg) const {
// WCAG 2.1 relative luminance calculation
auto luminance = [](const QColor& c) -> float {
auto toLinear = [](float v) -> float {
v /= 255.0f;
return v <= 0.03928f ? v / 12.92f : std::pow((v + 0.055f) / 1.055f, 2.4f);
};
float r = toLinear(c.red());
float g = toLinear(c.green());
float b = toLinear(c.blue());
return 0.2126f * r + 0.7152f * g + 0.0722f * b;
};
float l1 = luminance(fg);
float l2 = luminance(bg);
float lighter = std::max(l1, l2);
float darker = std::min(l1, l2);
return (lighter + 0.05f) / (darker + 0.05f);
}
void ColorSchemePage::updateAllColors() {
if (!theme_)
return;
auto& colorScheme = const_cast<cf::ui::core::MaterialColorScheme&>(
static_cast<const cf::ui::core::MaterialColorScheme&>(theme_->color_scheme()));
for (auto& info : colorCards_) {
QColor color = colorScheme.queryColor(info.token.toStdString().c_str());
QString pairToken = getContrastPair(info.token);
QColor pairColor = colorScheme.queryColor(pairToken.toStdString().c_str());
float contrast = calculateContrastRatio(color, pairColor);
QString contrastInfo = QString("⚡ %1").arg(contrast, 0, 'f', 2);
info.widget->updateColor(color, contrastInfo);
}
}
void ColorSchemePage::updateWindowTheme() {
if (!theme_)
return;
auto& colorScheme = const_cast<cf::ui::core::MaterialColorScheme&>(
static_cast<const cf::ui::core::MaterialColorScheme&>(theme_->color_scheme()));
// Update background colors
QColor bg = colorScheme.queryColor("md.background");
QColor onSurface = colorScheme.queryColor("md.onSurface");
scrollContent_->setAutoFillBackground(true);
QPalette pal = scrollContent_->palette();
pal.setColor(QPalette::Window, bg);
scrollContent_->setPalette(pal);
}
void ColorSchemePage::applyTheme(const cf::ui::core::ICFTheme& theme) {
// Store theme pointer
theme_ = &theme;
// Clear existing cards and recreate with new theme
colorCards_.clear();
// Clear layout items - this also deletes the widgets (Qt takes ownership)
while (colorGridLayout_->count()) {
QLayoutItem* item = colorGridLayout_->takeAt(0);
if (item->widget()) {
delete item->widget();
}
delete item;
}
createColorGroups();
updateAllColors();
updateWindowTheme();
}
void ColorSchemePage::onColorCardClicked(const QString& hexValue) {
QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(hexValue);
showToast(QString("已复制: %1").arg(hexValue));
}
void ColorSchemePage::showToast(const QString& message) {
delete toast_;
toast_ = new ToastWidget(message, this);
toast_->show();
}
} // namespace cf::ui::gallery
Updated on 2026-03-09 at 10:14:01 +0000