2026, Jan 04 21:01

Почему QSS в PySide6 не перекрашивает SVG-иконки и как правильно переключать темы

Почему в PySide6 QSS не меняет цвет SVG-иконок в QIcon, и как грамотно переключать темы: два набора иконок и QDir.setSearchPaths() без костылей. Подробно.

Переключение приложения PySide6 между светлой и тёмной темами почти всегда приводит к одному и тому же вопросу: можно ли управлять цветом SVG-иконок через QSS — по аналогии с браузерами и currentColor? Короткий ответ: нет. И если ваш интерфейс опирается на QIcon с SVG-ресурсами, попытка унаследовать цвет из таблицы стилей не даст ожидаемого результата.

Постановка задачи

Легко предположить, что правило стиля ниже перекрасит SVG-иконку, используемую виджетом, особенно если внутри SVG задано fill="currentColor" и вы рассчитываете, что иконка унаследует свойство color:

#nav_btn {
  color: white;
}

На практике в PySide6 это не работает. SVG, загруженные через QIcon, не получают контекста о цвете виджета, поэтому иконка не изменится вместе со свойством color в QSS.

Почему так происходит

QIcon — это не изображение, а абстракция, к которой виджеты обращаются, когда им нужен pixmap определённого размера или состояния. Под капотом у каждого QIcon есть движок иконок; для SVG он умеет парсить и рендерить содержимое SVG в pixmap по запросу.

Когда виджет рисует свою иконку, Qt вызывает QIcon::pixmap(), который перенаправляет запрос в движок. Ключевой момент — сигнатура, которую получает движок:

QIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)

В этом вызове нет никакой информации о цвете переднего плана виджета или его таблице стилей. Движок иконок рендерит, не зная, какой цвет задан у целевого виджета, поэтому изменение палитры или свойства color в QSS не влияет на результат. Да, есть ещё функция paint(), которая получает QPainter, но полагаться на неё для передачи цвета в данном случае — неподдерживаемый путь; даже собственная обработка style sheet в Qt для иконок на кнопках использует именно pixmap(). В итоге «перекрашивание» иконок через QSS здесь недостижимо.

Ещё один частый источник путаницы — ожидание, что таблицы стилей Qt ведут себя как CSS один к одному. Синтаксис похож, но это не браузерный движок: полной каскадности и всех семантик CSS там нет. В частности, нельзя передать «CSS-контекст» цвета в движок иконок.

Практическое решение

Надёжный подход — держать отдельные наборы иконок под каждую цветовую схему и переключать их на лету. В Qt для удобства есть небольшой, но полезный механизм: QDir.setSearchPaths(). Вы задаёте логический префикс и сопоставляете его с физической директорией в зависимости от активной темы. Тогда один и тот же код загрузки будет брать нужные ресурсы без условной логики по всему UI.

Последовательность такая: подготовьте две папки с одинаковыми именами SVG-файлов — для светлой и для тёмной темы — и привяжите префикс поискового пути к активной папке в соответствии с текущей схемой. Работает как с файловой системой, так и с ресурсами Qt.

Вот инициализация:

ui_meta = QApplication.styleHints()
if ui_meta.colorScheme() == Qt.ColorScheme.Dark:
    asset_dir = '<path-to-light-icons>'
else:
    asset_dir = '<path-to-dark-icons>'
QDir.setSearchPaths('gfx', [asset_dir])

После этого назначать иконки просто и независимо от физического расположения файлов:

main_button.setIcon(QIcon('gfx:toolbar_play.svg'))

Важно помнить: setSearchPaths() не обновляет задним числом уже созданные экземпляры QIcon. Вызывайте его до создания любых иконок — обычно при старте приложения. Если вы поддерживаете переключение темы на ходу, переустанавливайте иконки, вызывая setIcon() после смены схемы. Держите только логические имена и повторно применяйте их при изменении.

Минимальный пример переустановки иконки после смены системной цветовой схемы:

hint_src = QApplication.styleHints()
icon_key = 'toolbar_play.svg'

main_button.setIcon(QIcon('gfx:' + icon_key))

def on_scheme_flip():
    main_button.setIcon(QIcon('gfx:' + icon_key))

hint_src.colorSchemeChanged.connect(on_scheme_flip)

Почему это важно

В Qt рендеринг иконок определяется QIcon и его движком, а не CSS-подобной каскадностью. Ожидание, что QSS перекрасит SVG, ведёт в тупик и к хрупким костылям. Разделение ассетов по темам и выбор через QDir.setSearchPaths() делает код чище, предсказуемее и поддерживаемее, а решение о том, какой набор активен, — централизованным.

Итоги

Если ваше приложение на PySide6 поддерживает светлый и тёмный режимы, не рассчитывайте на свойство color в QSS для влияния на SVG-иконки. Подготовьте два набора иконок с одинаковыми именами файлов, выберите активный через QDir.setSearchPaths() до создания иконок, а при смене темы на лету переустановите иконки, чтобы они подхватили новый путь поиска. Такой подход соответствует реальной работе QIcon и QIconEngine и избавляет от привязки рендеринга иконок к неподдерживаемым ожиданиям от стилей.