
Я по горло сыт стандартно выглядящими приложениями. Сегодня все десктопные приложения Windows выглядят одинаково, да и внутри устроены одинаково: их создают на основе дурацких браузерных обёрток React, Electron, electronbun и Tauri, имитирующих реальные десктопные приложения. Они медленно работают и занимают кучу памяти — по сути, это bloatware. Блокнот — это, блин, приложение для простых ЗАМЕТОК, а не замена Word, калькулятор — это калькулятор, а не планировщик лунной миссии НАСА. На каком-то этапе Microsoft сбилась с курса, как будто сдалась и передала бразды правления куче веб-разработчиков, незнакомых с концепцией оптимизации.
Чёртов Блокнот занимает в памяти почти 50 МБ, хотя эквивалентное приложение, написанное на чистом Win32 C, занимает 1,8 МБ. Вроде бы, по современным меркам 50 МБ — это не так много, но в том-то и смысл: эти мегабайты постепенно накапливаются. Недавно я купил новый Intel Ultra 9 285 с 32 ГБ ОЗУ, но при запуске Windows 11 память уже была заполнена на 77%.

Программирование на Win32 API — утерянное ныне искусство; я с ностальгией вспоминаю, как когда-то программировали приложения для Windows. Процесс был запутанным, но обеспечивал полный контроль.
Программирование современных UI чаще всего пытается спрятать от нас операционную систему. Мы привязаны к прямоугольной коробке, хотя в эпоху Windows XP был период, когда крутыми считались нестандартные окна приложений, такое было даже у Windows Media Player.

Не все окна обязаны быть скучными скруглёнными прямоугольниками с боковой панелью, шестерёнкой настроек и спрятанным под ними веб-стеком. Когда-то приложениям Windows дозволялось быть странными. Проигрыватели мультимедиа выглядели как приборы. По рабочему столу гуляли животные. Вспомогательные панели походили на пульты управления, игрушки, радиоприёмники или маленькие инопланетные интерфейсы. Окно не должно было обязательно иметь прямоугольную форму только потому, что таким было окно операционной системы.

Обычно смысл этого заключался не в удобстве пользования, а в индивидуальности.
В современных десктопных UI её практически не осталось. Не потому, что в Windows она больше невозможна, а потому, что большинство разработчиков теперь не программирует на том уровне, где они могут управлять самим окном.
Репозиторий GitHub к этому посту — небольшое напоминание о том, что Win32 по-прежнему позволяет нам это делать. Один пример из него делает окно овальным. Другой создаёт окно по форме растрового изображения, Третий превращает окно в анимированное животное, живущее на рабочем столе. Ни для одного из них не требуется огромный фреймворк, они просто работают с Windows напрямую.
Первое, что сбивает с толку людей, имеющих опыт в играх, браузерных приложениях и современных фреймворках UI — это то, что в центре внимания Win32 не находится цикл обновления, которым управляет разработчик. В нём всё зависит от сообщений. Приложение работает, а Windows постоянно обрабатывает его события:
while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); }
Этот цикл становится контрактом. Windows сообщает, что произошло какое-то событие, а процедура окна решает, что оно значит. «WM_CREATE» означает, что создаётся окно. «WM_PAINT» означает, что его нужно перерисовать. «WM_SIZE» означает, что изменилась клиентская область. «WM_DESTROY» означает, что работа завершена. Вот так по-настоящему выглядит программирование Win32: разработчик строит поведение из отдельных сообщений.
И как только понимаешь это, окна странной формы перестают быть загадкой.
Обычное окно верхнего уровня имеет прямоугольную форму, но у Windows также есть понятие объекта области (HRGN). Если при помощи SetWindowRgn присвоить окну область, то окном будет считаться только эта часть. Всё остальное исчезает: и визуально, и с точки зрения интерактивности. В этом и заключается хитрость.
Простейшая версия подобного находится в basic/main.c. Этот код создаёт окно без границ, а затем выполняет следующее:
region = CreateEllipticRgn(0, 0, rc.right, rc.bottom); SetWindowRgn(hwnd, region, TRUE);
Этого достаточно, чтобы превратить окно в овал. Не в поддельный овал, нарисованный внутри прямоугольной рамки, а в настоящий овальный HWND. В примере также обрабатывается одна практическая мелочь, о которой часто забывают: после удаления панели заголовка система больше не будет перетаскивать окно за нас. Поэтому при «WM_LBUTTONDOWN» код имитирует перетаскивание панели заголовка, передавая «WM_NCLBUTTONDOWN» с «HTCAPTION».
Эта маленькая деталь — миниатюрная версия всей истории с произвольными окнами. Создать форму просто. Настоящая работа заключается в том, чтобы заменить всё то, что обычное окно делало автоматически.

В следующем примере всё становится интереснее. drivenbyimage/main.c определяет форму не математически, а получает её из данных растрового изображения. Программа загружает shape.bmp, считывает пиксели при помощи GetDIBits, а затем построчно сканирует изображение. Каждый горизонтальный интервал непрозрачных пикселей становится небольшой прямоугольной областью, и эти интервалы объединяются в одну область окна.
#define TRANSPARENT_COLOR RGB(255, 0, 255)
Маджента обозначает пустое пространство, всё остальное становится частью окна.
Это значит, что растровое изображение одновременно выполняет две задачи. Во-первых, это то, что мы отрисовываем в «WM_PAINT». Во-вторых, это форма самого окна. Именно так работали старые приложения с возможностью смены скинов. Разработчики не были ограничены кругами, скруглёнными углами и красивой векторной геометрией. Окно могло принять форму изображения, на котором нарисована собака, космический корабль, магнитофон или мультяшное лицо.
Отчасти благодаря этому старое десктопное ПО было таким интересным. Прямоугольник окна переставал быть законом природы и становился лишь одним из вариантов.
Однако области, создаваемые на основе растровых изображений, всё равно имели резкую границу. Пиксель или был, или его не было. Это отлично подходит для силуэтов или вырезанных UI, но если вам нужны плавные границы, просвечивающие пиксели или анимация, то приходилось переходить к многослойным окнам.
Именно эту задачу решает пример Animated/. Я нашёл на itch.io красивый лист спрайтов с анимированной 8-битной собакой авторства художника inmenus, который передал свою работу в Creative Commons. Спасибо ему за это.
Вместо того, чтобы вырезать окно из области, пример создаёт всплывающее окно «WS_EX_LAYERED» и при помощи UpdateLayeredWindow загружает в него 32-битное изображение с альфа-каналом. В примере используется лист спрайтов, кадры которого меняются по таймеру, выполняется отрисовка в битовую карту памяти при помощи GDI+, а затем результат передаётся на рабочий стол. Мы уже не говорим «окно будет эллипсом» или «окно соответствует этой маске», а сообщаем «окно будет тем, что представляют из себя эти пиксели».

Для анимации больше подходит многослойное окно (layered window), потому что благодаря нему мы получаем попиксельный альфа-канал, более плавные края, настоящую полупрозрачность и возможность менять видимую фигуру в каждом кадре.
Однако есть одна проблема с программированием Win32 API: при работе с произвольными окнами нужно всё делать самостоятельно, контролируя каждое сообщение Windows, а такая система ненадёжна.
Неприятности начинаются, когда мы решаем отказаться от обычного окна. Нам придётся реализовывать собственное перетаскивание, изменение размеров, поведение при закрытии, проверку нажатий мыши, обработку ввода с клавиатуры, правильность перерисовки, обработку DPI и все раздражающие маленькие пограничные случаи, которые для стандартного окна были решены десятки лет назад. Именно поэтому окна странной формы легко прототипировать и сложно доводить до ума.
Если в результате не получается что-то поистине выдающееся, пользователи обычно не ценят усилий.
Культура десктопного UI перестала считать крутыми безумные скины, она хочет, чтобы окна надёжно работали и не мешали процессу. Странные окна стали ассоциироваться с уловками, рекламным ПО, панелями инструментов и раздутыми утилитами, нежели с серьёзным ПО. И это печально.
Но мне всё равно нравится, что они существуют. Это напоминает мне о том, что когда-то Windows была платформой, на которой ПО могло иметь физическую идентичность, а не просто структуру страницы внутри замаскированной под приложение вкладки браузера. Чаще всего правильным выбором будет обычное прямоугольное окно, но полезно помнить, что это лишь один из вариантов, а не непреложный закон.
В Win32 здорово то, что он не пытается отговорить нас от этого. Он просто даёт нам сообщения, дескрипторы, API отрисовки и достаточно возможностей для создания чего-нибудь интересного.
Код к посту можно найти в репозитории GitHub.