Почему accessibility — это не про «слабаков», а про качество кода
Когда мы пишем на C++, мы привыкли думать о производительности, памяти и скорости. Но есть ещё один аспект, который часто упускают — accessibility. Это не просто модный термин из мира веб-разработки. В C++ доступность приложений — это возможность для людей с ограниченными возможностями (нарушения зрения, слуха, моторики) пользоваться вашим софтом наравне с другими.
Представьте: вы создали крутой графический редактор или консольную утилиту. Но если она не читается screen reader'ом или не управляется с клавиатуры — вы отсекаете 15–20% пользователей. А ещё accessibility — это часто требование закона (например, в США — Section 508, в Европе — EN 301 549).
В этой статье мы разберём, как внедрить a11y best practices (a11y — числовая аббревиатура accessibility) в C++ проекты, не жертвуя производительностью. Поехали!
С чего начать: базовые принципы инклюзивного кода
Прежде чем лезть в код, запомните три кита:
- Воспринимаемость — информация должна быть доступна хотя бы через одно чувство (зрение, слух, тактильно).
- Управляемость — интерфейс должен работать с клавиатуры, голосом или переключателями.
- Понятность — поведение элементов предсказуемо, ошибки понятны.
В C++ это особенно важно, потому что мы часто работаем с низкоуровневыми API, где семантика теряется. Например, рисуя кнопку через OpenGL, вы не получите автоматической поддержки screen reader'ов — её нужно добавлять вручную.
Семантическая разметка в C++ GUI: Qt и не только
Самый распространённый способ добавить accessibility в C++ — использовать фреймворк Qt. Он имеет встроенную поддержку через QAccessibleInterface. Но даже если вы пишете на чистом WinAPI — есть IAccessible.
Пример: делаем кнопку доступной
Допустим, у вас есть кастомная кнопка, отрисованная через QPainter. Без accessibility screen reader увидит просто «прямоугольник». Исправляем:
#include <QAccessible>
#include <QAccessibleWidget>
#include <QPushButton>
class AccessibleButton : public QAccessibleWidget {
public:
explicit AccessibleButton(QWidget* widget)
: QAccessibleWidget(widget, QAccessible::Button) {}
QString text(QAccessible::Text t) const override {
if (t == QAccessible::Name) {
auto* btn = qobject_cast<QPushButton*>(widget());
return btn ? btn->text() : QString();
}
return QAccessibleWidget::text(t);
}
};
// Использование:
QPushButton* btn = new QPushButton("Отправить");
QAccessible::registerAccessibleFactory(
[](QObject* obj) -> QAccessibleInterface* {
if (auto* btn = qobject_cast<QPushButton*>(obj)) {
return new AccessibleButton(btn);
}
return nullptr;
}
);
Теперь screen reader прочитает «Отправить, кнопка». Магия? Нет, просто инклюзивный код.
Accessibility в консольных приложениях C++
Казалось бы, консоль — это прошлый век. Но многие утилиты, CI/CD пайплайны и серверные приложения работают именно в терминале. Как сделать их доступными?
Правильный вывод для screen reader'ов
Screen reader (например, NVDA, JAWS) читает консоль построчно. Если вы выводите таблицу ASCII-символами — это кошмар. Лучше используйте структурный вывод:
#include <iostream>
#include <string>
#include <vector>
struct User {
std::string name;
int age;
};
void printAccessibleTable(const std::vector<User>& users) {
// Screen reader friendly: сначала заголовок, потом данные
std::cout << "Таблица пользователей:" << std::endl;
for (const auto& u : users) {
std::cout << "Имя: " << u.name
<< ", возраст: " << u.age << std::endl;
}
}
int main() {
std::vector<User> users = {{"Анна", 30}, {"Иван", 25}};
printAccessibleTable(users);
return 0;
}
Обратите внимание: мы не используем табуляцию или пробелы для выравнивания — это сбивает чтение. Вместо этого — естественный язык.
ARIA-аналоги для C++: как сообщить screen reader'у о состоянии
В вебе есть ARIA-атрибуты (role, aria-label, aria-live). В C++ десктопных приложениях их роль выполняют Accessible Role и State. Например, для индикатора загрузки:
class ProgressBarAccessible : public QAccessibleWidget {
public:
explicit ProgressBarAccessible(QWidget* widget)
: QAccessibleWidget(widget, QAccessible::ProgressBar) {}
QAccessible::State state() const override {
QAccessible::State s = QAccessibleWidget::state();
auto* bar = qobject_cast<QProgressBar*>(widget());
if (bar && bar->minimum() == 0 && bar->maximum() == 0) {
s.busy = true; // Неопределённый прогресс
}
return s;
}
QString text(QAccessible::Text t) const override {
if (t == QAccessible::Value) {
auto* bar = qobject_cast<QProgressBar*>(widget());
if (bar) return QString::number(bar->value()) + "%";
}
return QAccessibleWidget::text(t);
}
};
Теперь screen reader объявит: «Прогресс: 45%» и, если нужно, «Занято» для неопределённого прогресс-бара.
Обработка ошибок с точки зрения accessibility
Ошибки — это не просто std::cerr. Пользователь с нарушением зрения может не увидеть красный текст. Добавьте звуковые сигналы (через Beep() в Windows или QSound) и обязательно выводите описание ошибки в лог, который читает screen reader.
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#endif
void reportError(const std::string& message) {
// Визуальный вывод
std::cerr << "Ошибка: " << message << std::endl;
// Звуковой сигнал для screen reader
#ifdef _WIN32
Beep(440, 200); // Нота Ля, 200 мс
#endif
// Логирование для дальнейшего анализа
// logToFile(message);
}
Важно: не злоупотребляйте звуками — используйте их только для критических ошибок.
Тестирование доступности: как не накосячить
Вы всё написали, но как проверить? Есть несколько инструментов:
- NVDA (бесплатный screen reader) — пройдитесь по всему интерфейсу.