Введение
Для коммерческого продукта часто важно последовательное, узнаваемое визуальное оформление — фирменные цвета, шрифты, формы элементов, согласованные по всему приложению. В статье разберём, как организовать QSS-стили как структурированную дизайн-систему, а не разрозненный набор отдельных правил, что особенно важно для крупных приложений с десятками экранов, разрабатываемых командой из нескольких человек.
Концепция
Дизайн-система на уровне QSS обычно строится вокруг небольшого набора базовых «токенов» — переменных, определяющих фирменные цвета, типографику, отступы и радиусы скругления — и набора переиспользуемых стилевых «компонентов» (стиль первичной кнопки, стиль вторичной кнопки, стиль карточки), определённых через комбинацию селекторов по динамическим свойствам, аналогично подходу из статьи 260. Централизация этих значений в одном месте (как в ThemeManager из статьи 265, но расширенная сверх просто цветов тёмной/светлой темы до полноценной системы токенов) гарантирует визуальную согласованность и облегчает последующие изменения дизайна.
Пример кода: Брендирование приложения
// designtokens.h — централизованные "токены" дизайн-системы бренда
#pragma once
#include <QString>
namespace DesignTokens
{
// Фирменные цвета — единственное место в коде, где они определены
inline const QString BrandPrimary = "#7c3aed";
inline const QString BrandPrimaryHover = "#6d28d9";
inline const QString BrandSecondary = "#f3f4f6";
inline const QString TextPrimary = "#111827";
inline const QString TextSecondary = "#6b7280";
inline const QString BorderColor = "#e5e7eb";
inline const QString DangerColor = "#dc2626";
// Типографика
inline const QString FontFamily = "\"Inter\", \"Segoe UI\", sans-serif";
inline const int FontSizeBody = 14;
inline const int FontSizeHeading = 20;
// Отступы и формы — последовательная шкала вместо произвольных значений
inline const int SpacingSmall = 8;
inline const int SpacingMedium = 16;
inline const int SpacingLarge = 24;
inline const int RadiusDefault = 6;
}
// designsystem.h — генерация единого QSS на основе токенов, с компонентами через свойства
#pragma once
#include "designtokens.h"
#include <QString>
inline QString buildApplicationStyleSheet()
{
using namespace DesignTokens;
return QString(R"(
QWidget {
font-family: %1;
font-size: %2px;
color: %3;
}
/* Компонент "первичная кнопка" — главное действие на экране, используется через свойство */
QPushButton[variant="primary"] {
background-color: %4;
color: white;
border: none;
border-radius: %5px;
padding: %6px %7px;
font-weight: 600;
}
QPushButton[variant="primary"]:hover {
background-color: %8;
}
/* Компонент "вторичная кнопка" — менее значимое действие */
QPushButton[variant="secondary"] {
background-color: %9;
color: %3;
border: 1px solid %10;
border-radius: %5px;
padding: %6px %7px;
}
/* Компонент "опасное действие" — для удаления и аналогичных деструктивных операций */
QPushButton[variant="danger"] {
background-color: transparent;
color: %11;
border: 1px solid %11;
border-radius: %5px;
padding: %6px %7px;
}
QLabel[variant="heading"] {
font-size: %12px;
font-weight: 700;
color: %3;
}
QLabel[variant="caption"] {
color: %13;
font-size: 12px;
}
)").arg(FontFamily).arg(FontSizeBody).arg(TextPrimary)
.arg(BrandPrimary).arg(RadiusDefault).arg(SpacingSmall).arg(SpacingMedium * 1.25)
.arg(BrandPrimaryHover).arg(BrandSecondary).arg(BorderColor)
.arg(DangerColor).arg(FontSizeHeading).arg(TextSecondary);
}
// Использование компонентов дизайн-системы через свойства — единообразно по всему приложению
auto *saveButton = new QPushButton("Сохранить");
saveButton->setProperty("variant", "primary");
auto *cancelButton = new QPushButton("Отмена");
cancelButton->setProperty("variant", "secondary");
auto *deleteButton = new QPushButton("Удалить аккаунт");
deleteButton->setProperty("variant", "danger");
auto *titleLabel = new QLabel("Настройки профиля");
titleLabel->setProperty("variant", "heading");
Пояснения к коду
DesignTokens собирает все фирменные значения (цвета, шрифты, отступы) в одном файле — любое изменение бренда (например, смена основного фирменного цвета) требует правки ровно в одном месте, а не поиска и замены повторяющихся значений по всему кодовой базе разрозненных QSS-строк. buildApplicationStyleSheet() генерирует итоговый QSS, подставляя токены через QString::arg(), и определяет набор переиспользуемых «компонентов» дизайн-системы (variant="primary", variant="secondary", variant="danger") как стандартизированные визуальные роли, используемые единообразно на всех экранах приложения.
Использование setProperty("variant", "primary") в коде, создающем конкретные кнопки, показывает декларативный, основанный на роли подход — разработчик, создающий новый экран, просто выбирает подходящую семантическую роль («это главное действие», «это опасное действие») для каждого элемента, не задумываясь каждый раз заново о конкретных пиксельных значениях отступов или точном оттенке цвета — вся эта детализация уже зафиксирована один раз в дизайн-системе.
Подводные камни
QString::arg()с большим количеством позиционных аргументов (%1,%2, …%13в примере) подвержен ошибкам при добавлении или удалении токенов — несовпадение порядка вызовов.arg()с порядком плейсхолдеров в строке шаблона приводит к тихой, не вызывающей ошибку компиляции подстановке неправильных значений на неправильные места; для дизайн-систем с большим количеством токенов стоит рассмотреть более явный, защищённый от такой путаницы способ подстановки (например, черезQString::replace()с именованными плейсхолдерами, как в статье 265).- Использование
variant-свойств без единого, документированного списка допустимых значений может привести к постепенному, неконтролируемому разрастанию числа вариантов, когда разные разработчики команды независимо придумывают новые, слегка отличающиеся друг от друга роли вместо переиспользования уже существующих — для эффективной дизайн-системы важна не только техническая реализация через QSS, но и согласованная договорённость команды об ограниченном, осознанно поддерживаемом наборе компонентов. - Отсутствие единого визуального источника истины (например, актуального демонстрационного экрана со всеми вариантами компонентов дизайн-системы) для проверки, что изменение токенов не сломало визуальный вид где-то неожиданно — для дизайн-систем, используемых в большом приложении, полезно держать отдельный, тестовый экран, демонстрирующий все варианты всех компонентов одновременно, что облегчает визуальную проверку после любых изменений токенов или шаблона стилей.
- Дизайн-система, спроектированная и захардкоженная только для одной конкретной темы (светлой), без учёта последующей необходимости поддержки тёмной темы — токены и генерация стилей из этой статьи и подход к темам из статьи 265 логически тесно связаны, и при проектировании дизайн-системы с нуля стоит сразу продумывать структуру, совместимую с несколькими наборами цветовых значений, а не привязывать токены жёстко к одной единственной цветовой палитре, которую сложно будет параметризовать впоследствии.