Введение
Слабовидящим пользователям часто нужен увеличенный размер текста и элементов интерфейса — операционные системы предоставляют системные настройки масштабирования (DPI scaling, увеличение размера шрифта по умолчанию), и приложение должно корректно реагировать на эти настройки, а не использовать фиксированные, не масштабируемые размеры. В статье разберём, как обеспечить, чтобы Qt-приложение корректно адаптировалось к системным настройкам масштабирования и увеличенному размеру шрифта.
Концепция
Qt поддерживает автоматическое масштабирование интерфейса под системный DPI через переменные окружения и атрибуты приложения (Qt::AA_EnableHighDpiScaling, актуальный с Qt 5, включённый по умолчанию в Qt 6) — большинство стандартных виджетов и layout-based интерфейсов масштабируются корректно «из коробки» при условии, что размеры заданы в логических, а не жёстко зафиксированных физических пикселях. Проблемы возникают там, где разработчик явно захардкодил пиксельные размеры (setFixedSize(200, 30)) вместо использования относительных, основанных на метриках шрифта размеров, или там, где приложение игнорирует системную настройку увеличенного размера шрифта по умолчанию (Default Font Size в настройках ОС).
Пример кода: Поддержка увеличения текста
#include <QApplication>
#include <QFontMetrics>
#include <QPushButton>
#include <QLineEdit>
// ПРОБЛЕМНО: фиксированные пиксельные размеры не учитывают
// увеличенный пользователем масштаб или размер шрифта по умолчанию
void createButtonBadPractice(QWidget *parent)
{
auto *button = new QPushButton("Сохранить", parent);
button->setFixedSize(100, 28); // при увеличенном шрифте текст не уместится
}
// ЛУЧШЕ: размер кнопки рассчитывается на основе реальных метрик текущего шрифта,
// автоматически адаптируясь под системные настройки масштабирования
void createButtonGoodPractice(QWidget *parent)
{
auto *button = new QPushButton("Сохранить", parent);
const QFontMetrics metrics(button->font());
const int textWidth = metrics.horizontalAdvance(button->text());
const int textHeight = metrics.height();
// Минимальный размер с запасом на внутренние отступы кнопки,
// вместо абсолютного фиксированного значения
button->setMinimumSize(textWidth + 40, textHeight + 16);
}
// main.cpp — корректная инициализация поддержки масштабирования
#include <QApplication>
int main(int argc, char *argv[])
{
// В Qt 6 высокое DPI-масштабирование включено по умолчанию,
// но явное указание политики масштабирования повышает предсказуемость
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
QApplication app(argc, argv);
// Уважение системного размера шрифта по умолчанию вместо
// принудительной установки конкретного фиксированного размера шрифта приложения
QFont systemDefaultFont = QApplication::font();
qDebug() << "Используется системный шрифт по умолчанию:" << systemDefaultFont.family()
<< "размер:" << systemDefaultFont.pointSize();
return app.exec();
}
// Пример масштабируемого QML-интерфейса с использованием относительных,
// а не абсолютных пиксельных размеров для текста
import QtQuick
Rectangle {
width: 300; height: 100
Text {
anchors.centerIn: parent
text: "Пример текста"
// font.pixelSize фиксирован в пикселях — не масштабируется
// системными настройками; font.pointSize лучше следует
// системным настройкам размера шрифта по умолчанию
font.pointSize: 12
}
}
Пояснения к коду
createButtonBadPractice() демонстрирует проблемный паттерн: setFixedSize(100, 28) — абсолютное, не зависящее от реального содержимого и текущих настроек шрифта значение; если пользователь увеличил системный размер шрифта по умолчанию (стандартная настройка ОС для слабовидящих), текст кнопки физически не уместится в фиксированную область, будет обрезан или начнёт выходить за границы виджета. createButtonGoodPractice() решает это, вычисляя минимальный размер динамически через QFontMetrics, основанный на реальной ширине текста при текущем шрифте — при изменении системного размера шрифта (что автоматически отражается на button->font()) пересчёт размера произойдёт корректно.
setHighDpiScaleFactorRoundingPolicy() с политикой PassThrough — явный выбор поведения масштабирования при нецелочисленных коэффициентах масштабирования (например, 150%), обеспечивающий более предсказуемое, плавное масштабирование по сравнению с поведением по умолчанию, которое может округлять коэффициент до ближайшего целого числа, давая визуально менее точное соответствие системной настройке. В QML-примере явно указано различие между font.pixelSize (абсолютный размер в пикселях, не реагирующий на изменения системных настроек размера шрифта) и font.pointSize (относительная единица, которая следует системным настройкам шрифта по умолчанию) — выбор второго варианта для основного текста интерфейса значительно лучше поддерживает потребности пользователей, увеличивших системный шрифт.
Подводные камни
- Массовое использование
font.pixelSizeвместоfont.pointSizeв QML-коде — частая практика среди разработчиков, не задумывающихся о доступности, посколькуpixelSizeдаёт более предсказуемый, «пиксель в пиксель» визуальный результат на конкретном экране разработки, но именно эта предсказуемость означает полное игнорирование пользовательских настроек размера шрифта системы, что напрямую вредит слабовидящим пользователям. - Тестирование интерфейса только при стандартном (100%) масштабировании и стандартном размере шрифта. Многие проблемы масштабирования (обрезанный текст, перекрывающиеся элементы, неработающие layout’ы) проявляются только при увеличенных настройках (125%, 150%, 200% масштаба, или увеличенный на 1-2 шага размер шрифта по умолчанию) — без явного тестирования именно в этих условиях такие проблемы легко остаются незамеченными до релиза.
- Жёстко заданные минимальные размеры окна или диалогов, не учитывающие, что при увеличенном масштабе тот же визуальный контент требует больше физического пространства. Диалог, спроектированный с минимальным размером, едва достаточным для контента при 100% масштабе, может оказаться слишком маленьким для размещения того же контента без обрезки или появления полос прокрутки при увеличенном масштабе — минимальные размеры стоит либо вычислять динамически на основе реального содержимого (аналогично примеру с кнопкой), либо оставлять заведомый запас.
- Layout’ы, рассчитанные на конкретное соотношение пропорций элементов при стандартном размере шрифта, могут «разваливаться» непропорционально при увеличении размера текста — например, длинная подпись радиокнопки, занимавшая одну строку при обычном размере шрифта, при увеличении может перенестись на несколько строк, что не было предусмотрено в исходном расчёте высоты строки layout’а, и из-за этого может произойти визуальное перекрытие с соседними элементами, если высота строки была зафиксирована, а не вычислена динамически.