example/gui/material/material_typography/MaterialTypographyMainWindow.cpp¶
Material Design 3 Typography Gallery - Implementation.
Namespaces¶
| Name |
|---|
| cf |
| cf::ui |
| cf::ui::gallery |
Source code¶
#include "MaterialTypographyMainWindow.h"
#include "material/material_factory.hpp"
#include "ui/core/token/typography/cfmaterial_typography_token_literals.h"
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPropertyAnimation>
#include <QSize>
// Global namespace alias for token literals
namespace token_literals = ::cf::ui::core::token::literals;
namespace cf::ui::gallery {
// =============================================================================
// Static Token Lists
// =============================================================================
const QStringList MaterialTypographyMainWindow::DISPLAY_TOKENS = {
token_literals::TYPOGRAPHY_DISPLAY_LARGE, token_literals::TYPOGRAPHY_DISPLAY_MEDIUM,
token_literals::TYPOGRAPHY_DISPLAY_SMALL};
const QStringList MaterialTypographyMainWindow::HEADLINE_TOKENS = {
token_literals::TYPOGRAPHY_HEADLINE_LARGE, token_literals::TYPOGRAPHY_HEADLINE_MEDIUM,
token_literals::TYPOGRAPHY_HEADLINE_SMALL};
const QStringList MaterialTypographyMainWindow::TITLE_TOKENS = {
token_literals::TYPOGRAPHY_TITLE_LARGE, token_literals::TYPOGRAPHY_TITLE_MEDIUM,
token_literals::TYPOGRAPHY_TITLE_SMALL};
const QStringList MaterialTypographyMainWindow::BODY_TOKENS = {
token_literals::TYPOGRAPHY_BODY_LARGE, token_literals::TYPOGRAPHY_BODY_MEDIUM,
token_literals::TYPOGRAPHY_BODY_SMALL};
const QStringList MaterialTypographyMainWindow::LABEL_TOKENS = {
token_literals::TYPOGRAPHY_LABEL_LARGE, token_literals::TYPOGRAPHY_LABEL_MEDIUM,
token_literals::TYPOGRAPHY_LABEL_SMALL};
// =============================================================================
// ToastWidget Implementation
// =============================================================================
ToastWidget::ToastWidget(const QString& message, QWidget* parent)
: QWidget(parent), message_(message) {
setAttribute(Qt::WA_TransparentForMouseEvents, false);
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
setAttribute(Qt::WA_TranslucentBackground);
}
void ToastWidget::show(int durationMs) {
// Calculate size and position
QFont font("Segoe UI", 10);
QFontMetrics fm(font);
int padding = 20;
int textWidth = fm.horizontalAdvance(message_);
int width = textWidth + padding * 2;
int height = fm.height() + padding * 2;
setFixedSize(width, height);
// Center at bottom of parent
if (parentWidget()) {
QPoint pos = parentWidget()->mapToGlobal(parentWidget()->rect().bottomLeft());
int x = pos.x() + (parentWidget()->width() - width) / 2;
int y = pos.y() - height - 20;
move(x, y);
}
QWidget::show();
// Auto hide after duration
QTimer::singleShot(durationMs, this, [this]() {
// Fade out animation
QPropertyAnimation* anim = new QPropertyAnimation(this, "windowOpacity");
anim->setDuration(300);
anim->setStartValue(1.0);
anim->setEndValue(0.0);
connect(anim, &QPropertyAnimation::finished, this, [this]() {
hide();
deleteLater();
});
anim->start(QPropertyAnimation::DeleteWhenStopped);
});
}
void ToastWidget::paintEvent(QPaintEvent* event) {
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Rounded rectangle background
QRectF rect = this->rect().adjusted(2, 2, -2, -2);
QPainterPath path;
path.addRoundedRect(rect, 8, 8);
// Semi-transparent black background
QColor bgColor(0, 0, 0, 200);
painter.fillPath(path, bgColor);
// White text
painter.setPen(Qt::white);
QFont font("Segoe UI", 10);
painter.setFont(font);
QRect textRect = rect.toRect();
painter.drawText(textRect, Qt::AlignCenter, message_);
}
// =============================================================================
// FontCardWidget Implementation
// =============================================================================
FontCardWidget::FontCardWidget(const QString& tokenName, const QFont& font, float lineHeight,
const QString& previewText, QWidget* parent)
: QWidget(parent), tokenName_(tokenName), font_(font), lineHeight_(lineHeight),
previewText_(previewText) {
cssStyle_ = generateCssStyle();
setCursor(Qt::PointingHandCursor);
// Calculate minimum height based on font size
QFontMetrics fm(font_);
int minHeight = qMax(120, fm.height() * 3 + 60); // Enough space for preview + props
setMinimumHeight(minHeight);
}
void FontCardWidget::updateFont(const QFont& font, float lineHeight) {
font_ = font;
lineHeight_ = lineHeight;
cssStyle_ = generateCssStyle();
update();
}
QString FontCardWidget::generateCssStyle() const {
QString weightStr;
switch (font_.weight()) {
case QFont::Thin:
weightStr = "100";
break;
case QFont::ExtraLight:
weightStr = "200";
break;
case QFont::Light:
weightStr = "300";
break;
case QFont::Normal:
weightStr = "400";
break;
case QFont::Medium:
weightStr = "500";
break;
case QFont::DemiBold:
weightStr = "600";
break;
case QFont::Bold:
weightStr = "700";
break;
case QFont::ExtraBold:
weightStr = "800";
break;
case QFont::Black:
weightStr = "900";
break;
default:
weightStr = "400";
break;
}
return QString("font-family: '%1'; font-size: %2sp; font-weight: %3; line-height: %4sp;")
.arg(font_.family())
.arg(font_.pointSizeF())
.arg(weightStr)
.arg(lineHeight_);
}
QSize FontCardWidget::sizeHint() const {
QFontMetrics fm(font_);
QFont tokenFont("Segoe UI", 24);
QFontMetrics tokenFm(tokenFont);
int tokenHeight = tokenFm.height();
int propsHeight = fm.height() + 8;
int padding = 16 * 2; // Both sides
int spacing = 8; // Between elements
int height = tokenHeight + spacing + fm.height() * 2 + spacing + propsHeight + padding;
int width = 200; // Minimum width
return QSize(width, height);
}
void FontCardWidget::paintEvent(QPaintEvent* event) {
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QRectF cardRect = rect().adjusted(4, 4, -4, -4);
// Card background with shadow effect when hovered
if (isHovered_) {
QPainterPath path;
path.addRoundedRect(cardRect, 12, 12);
// Shadow
QPainterPath shadowPath = path.translated(0, 2);
QColor shadowColor(0, 0, 0, 20);
painter.fillPath(shadowPath, shadowColor);
// Border
QPen borderPen(QColor(100, 100, 255), 2);
painter.setPen(borderPen);
painter.drawPath(path);
}
// Background fill
QPainterPath path;
path.addRoundedRect(cardRect, 12, 12);
painter.fillPath(path, QColor(250, 250, 250));
// Content padding
const int padding = 16;
QRectF contentRect = cardRect.adjusted(padding, padding, -padding, -padding);
// Calculate heights dynamically
QFont tokenFont("Segoe UI", 24);
QFontMetrics tokenFm(tokenFont);
int tokenHeight = tokenFm.height();
int tokenY = contentRect.top();
// Token name (small, gray)
painter.setPen(QColor(120, 120, 120));
painter.setFont(tokenFont);
painter.drawText(contentRect.adjusted(0, tokenY, 0, 0), Qt::AlignLeft, tokenName_);
// Font preview - calculate available space
QFontMetrics fm(font_);
int propsHeight = fm.height() + 8; // Props at bottom
int availableHeight = static_cast<int>(contentRect.height()) - tokenHeight - propsHeight - 20;
int previewHeight = qMax(fm.height() * 2, availableHeight);
QRectF previewRect(contentRect.left(), tokenY + tokenHeight + 8, contentRect.width(),
static_cast<qreal>(previewHeight));
painter.setFont(font_);
painter.setPen(QColor(30, 30, 30));
// Draw preview text with elision
QString wrappedText =
fm.elidedText(previewText_, Qt::ElideRight, static_cast<int>(previewRect.width()));
painter.drawText(previewRect, Qt::AlignLeft | Qt::AlignVCenter, wrappedText);
// Font properties - fixed distance below preview
painter.setPen(QColor(100, 100, 100));
QFont propsFont("Segoe UI", 9);
painter.setFont(propsFont);
QString props =
QString("%1sp / %2sp / W%3").arg(font_.pointSizeF()).arg(lineHeight_).arg(font_.weight());
QRectF propsRect = contentRect;
propsRect.setBottom(cardRect.bottom() - 9);
painter.drawText(propsRect, Qt::AlignLeft | Qt::AlignTop, props);
// "Click to copy" hint
if (isHovered_) {
painter.setPen(QColor(100, 100, 255));
painter.drawText(propsRect, Qt::AlignRight | Qt::AlignTop, "点击复制 CSS");
}
}
void FontCardWidget::enterEvent(QEnterEvent* event) {
Q_UNUSED(event)
isHovered_ = true;
update();
}
void FontCardWidget::leaveEvent(QEvent* event) {
Q_UNUSED(event)
isHovered_ = false;
update();
}
void FontCardWidget::mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
emit clicked(cssStyle_);
}
}
// =============================================================================
// MaterialTypographyMainWindow Implementation
// =============================================================================
MaterialTypographyMainWindow::MaterialTypographyMainWindow(QWidget* parent) : QMainWindow(parent) {
// Create typography
typography_ = cf::ui::core::material::defaultTypography();
setupUI();
createFontGroups();
}
MaterialTypographyMainWindow::~MaterialTypographyMainWindow() = default;
void MaterialTypographyMainWindow::setupUI() {
// Central widget
centralWidget_ = new QWidget(this);
setCentralWidget(centralWidget_);
// Main layout
mainLayout_ = new QVBoxLayout(centralWidget_);
mainLayout_->setContentsMargins(20, 20, 20, 20);
mainLayout_->setSpacing(16);
// Header
createHeader();
// Scroll area for font cards
scrollArea_ = new QScrollArea(this);
scrollArea_->setWidgetResizable(true);
scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea_->setFrameShape(QFrame::NoFrame);
scrollContent_ = new QWidget();
scrollLayout_ = new QVBoxLayout(scrollContent_);
scrollLayout_->setContentsMargins(0, 0, 0, 0);
scrollLayout_->setSpacing(24);
// Grid layout for font cards
fontGridLayout_ = new QGridLayout();
fontGridLayout_->setSpacing(16);
scrollLayout_->addLayout(fontGridLayout_);
// Add stretch at bottom
scrollLayout_->addStretch();
scrollArea_->setWidget(scrollContent_);
mainLayout_->addWidget(scrollArea_, 1);
// Background color
QPalette pal = palette();
pal.setColor(QPalette::Window, QColor(245, 245, 245));
setPalette(pal);
}
void MaterialTypographyMainWindow::createHeader() {
headerLayout_ = new QHBoxLayout();
headerLayout_->setSpacing(16);
// Title
titleLabel_ = new QLabel("Material Design 3 Type Scale", this);
QFont titleFont("Segoe UI", 24, QFont::Bold);
titleLabel_->setFont(titleFont);
titleLabel_->setStyleSheet("QLabel { color: #1C1B1F; }");
headerLayout_->addWidget(titleLabel_);
headerLayout_->addStretch();
mainLayout_->addLayout(headerLayout_);
}
QString MaterialTypographyMainWindow::getPreviewText(const QString& token) const {
if (token.contains("Display")) {
return "Ag 你好世界 Display";
} else if (token.contains("Headline")) {
return "标题文字 Headline 大标题";
} else if (token.contains("Title")) {
return "标题文字 Title 标题";
} else if (token.contains("Body")) {
return "正文内容 The quick brown fox";
} else if (token.contains("Label")) {
return "标签 LABEL 按钮 Button";
}
return "Preview Text 预览";
}
void MaterialTypographyMainWindow::createFontGroups() {
int row = 0;
createFontGroup("Display Styles", fontGridLayout_, row, DISPLAY_TOKENS);
createFontGroup("Headline Styles", fontGridLayout_, row, HEADLINE_TOKENS);
createFontGroup("Title Styles", fontGridLayout_, row, TITLE_TOKENS);
createFontGroup("Body Styles", fontGridLayout_, row, BODY_TOKENS);
createFontGroup("Label Styles", fontGridLayout_, row, LABEL_TOKENS);
}
void MaterialTypographyMainWindow::createFontGroup(const QString& title, QGridLayout* layout,
int& row, const QStringList& tokens) {
// Section title label
QLabel* titleLabel = new QLabel(title);
QFont titleFont("Segoe UI", 14, QFont::DemiBold);
titleLabel->setFont(titleFont);
titleLabel->setStyleSheet("QLabel { color: #49454F; padding: 8px 0px; }");
layout->addWidget(titleLabel, row++, 0, 1, -1);
// Calculate column count
int cols = calculateColumnCount();
// Create font cards
int col = 0;
for (const QString& token : tokens) {
QFont font = typography_.queryTargetFont(token.toUtf8().constData());
float lineHeight = typography_.getLineHeight(token.toUtf8().constData());
QString previewText = getPreviewText(token);
FontCardWidget* card = new FontCardWidget(token, font, lineHeight, previewText);
// Connect click signal
connect(card, &FontCardWidget::clicked, this,
&MaterialTypographyMainWindow::onFontCardClicked);
// Store card info
fontCards_.append({card, token});
layout->addWidget(card, row, col);
col++;
if (col >= cols) {
col = 0;
row++;
}
}
if (col > 0) {
row++;
}
// Add spacing after group
row++;
}
int MaterialTypographyMainWindow::calculateColumnCount() const {
int width = scrollArea_->width();
if (width < 800)
return 2;
if (width < 1200)
return 3;
return 4;
}
void MaterialTypographyMainWindow::showToast(const QString& message) {
ToastWidget* toast = new ToastWidget(message, this);
toast->show();
}
void MaterialTypographyMainWindow::onFontCardClicked(const QString& cssStyle) {
// Copy to clipboard
QApplication::clipboard()->setText(cssStyle);
showToast("CSS 样式已复制到剪贴板");
}
} // namespace cf::ui::gallery
Updated on 2026-03-09 at 10:14:01 +0000