Пользовательский интерфейс в Qt

QSS в многоплатформенных приложениях: различия рендеринга

Введение

QSS задуман как кросс-платформенный механизм стилизации, но на практике итоговый визуальный результат может незначительно (а иногда и заметно) отличаться между Windows, macOS и Linux из-за различий в базовых стилях, шрифтовом рендеринге и поведении некоторых виджетов на разных платформах. В статье разберём конкретные источники таких различий и подходы к минимизации платформенной рассогласованности.

Концепция

QSS применяется поверх выбранного базового стиля (QStyle) — и если приложение использует платформенный нативный стиль по умолчанию (Windows Vista style на Windows, macOS style на Mac) вместо явно заданного кросс-платформенного Fusion, итоговый вид может заметно отличаться между платформами даже при идентичном QSS, поскольку часть визуальных аспектов (особенно тонкие детали отрисовки, не покрытые явно заданными QSS-свойствами) наследуется от платформенного стиля. Для максимально предсказуемого, одинакового на всех платформах вида обычно явно устанавливают стиль Fusion как базу, поверх которой QSS работает более последовательно.

Пример кода: QSS в многоплатформенных приложениях

#include <QApplication>
#include <QStyleFactory>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Явное указание кросс-платформенного стиля Fusion как основы —
    // существенно снижает платформенные различия в применении QSS
    app.setStyle(QStyleFactory::create("Fusion"));

    app.setStyleSheet(R"(
        QPushButton {
            background-color: #2563eb;
            color: white;
            border-radius: 4px;
            padding: 8px 16px;
            font-family: "Segoe UI", "San Francisco", "Ubuntu", sans-serif;
        }
    )");

    // ... остальной код приложения
    return app.exec();
}
// Платформенно-зависимая корректировка стиля там, где полностью унифицировать невозможно
#include <QSysInfo>

QString platformSpecificAdjustments()
{
#if defined(Q_OS_MACOS)
    // На macOS шрифты системы рендерятся визуально немного крупнее при том же размере в пунктах
    return "QWidget { font-size: 12px; }";
#elif defined(Q_OS_WIN)
    return "QWidget { font-size: 13px; }";
#else
    return "QWidget { font-size: 13px; }";
#endif
}

void applyPlatformAwareStyle(QApplication &app, const QString &baseStyleSheet)
{
    app.setStyleSheet(baseStyleSheet + platformSpecificAdjustments());
}

Пояснения к коду

Явный вызов QStyleFactory::create("Fusion") гарантирует, что приложение использует один и тот же базовый алгоритм отрисовки виджетов на всех платформах, вместо платформенно-специфичных стилей по умолчанию — это первый и самый важный шаг к визуальному единообразию между ОС. Список шрифтов через запятую в font-family ("Segoe UI", "San Francisco", "Ubuntu", sans-serif) показывает практику явного указания предпочитаемого шрифта для каждой платформы с fallback — Qt применит первый шрифт из списка, который доступен в системе, и в конце концов упадёт на обобщённый sans-serif, если ни один из конкретных вариантов не найден.

platformSpecificAdjustments() показывает осознанный, явный подход к минимальным платформенным корректировкам там, где полная унификация недостижима или нежелательна (например, известная разница в визуальном восприятии размера шрифта между macOS и Windows при формально одинаковом значении в пунктах) — такие корректировки должны быть осознанными исключениями, явно задокументированными в коде через комментарии, а не случайными артефактами недостаточного тестирования на разных платформах.

Подводные камни

  • Тестирование QSS только на одной платформе разработки (часто Windows или Linux) без проверки на остальных целевых платформах — поскольку именно платформенные различия в рендеринге шрифтов, поведении некоторых псевдосостояний и деталях нативного стиля могут проявиться только при реальном запуске на другой ОС, регулярная проверка внешнего вида на каждой целевой платформе должна быть частью обычного процесса разработки кросс-платформенного приложения, а не разовой финальной проверкой перед релизом.
  • Шрифты, доступные на одной платформе, могут отсутствовать на другой — жёстко закодированное указание единственного, специфичного для одной ОС шрифта (например, «Segoe UI» без альтернатив) приводит к тому, что на других платформах, где этот шрифт недоступен, Qt подставит совершенно другой шрифт по своему выбору, что может выглядеть не так, как задумывалось дизайном; список альтернатив с fallback, как в примере, существенно снижает этот риск.
  • HiDPI/масштабирование экрана обрабатывается по-разному на разных платформах, и фиксированные пиксельные значения в QSS (отступы, размеры в px) могут визуально выглядеть по-разному при разной плотности пикселей экрана конкретного устройства — для приложений, ориентированных на широкий спектр экранов с разной плотностью пикселей, стоит проверять вид интерфейса именно на HiDPI-дисплеях, а не предполагать автоматическую идеальную масштабируемость всех QSS-размеров.
  • Различное поведение системных диалогов (выбора файла, цвета, печати) между платформами, не подчиняющихся кастомному QSS-стилю приложения вовсе (аналогично подводному камню из статьи 265 про темы) — на одной платформе системный диалог может визуально хорошо сочетаться с остальным кастомизированным интерфейсом, а на другой — выглядеть совершенно инородно, и эта рассогласованность находится за пределами того, что можно исправить через сам QSS приложения.