brew install sdl2_ttf
sudo apt install libsdl2-ttf-dev
pacman -S mingw-w64-x86_64-SDL2_ttf
Перед сборкой: установи и активируй Emscripten SDK (один раз):
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
emcc пытается скачать/распаковать порт FreeType в системный кэш usr/share/emscripten/cache/ports, куда может не быть прав. Нужно направить кэш Emscripten в каталог, где можно писать. Это делает =wasm-setup=, при “замороженном” кэше emcc не имеет права ничего докачивать и требует уже готовый кэш. Разморозив его и указав путь, ты позволяешь emcc один раз скачать и собрать нужные порты в ~.emscripten_cache. Затем сборки make wasm
пойдут офлайн.
make wasm-setup
make wasm
make serve
firefox 127.0.0.1:8080
/src
platform/ // всё общение с SDL/OS
platform_sdl.c
platform_sdl.h
gfx/ // рендер и 2D-утилиты поверх SDL_Surface (но без SDL_Window!)
surface.c surface.h
text.c text.h // TTF, но наружу — только text_* API
core/ // независимое "ядро" WM (про окна, z-order, focus, damage)
wm.c wm.h
window.c window.h // интерфейс окна: vtable, базовые хелперы
damage.c damage.h
timing.c timing.h
input.c input.h // распределение событий по окнам, многопользовательский фокус
apps/ // конкретные типы окон (демо/виджеты)
win_paint.c win_paint.h
win_square.c win_square.h
win_console.c win_console.h
main.c
/include // публичные заголовки
- Создаёт окно ОС и SDL_Surface окна (screen).
- Держит собственный ARGB32 backbuffer (pf->back).
- Конвертирует SDL-события в абстрактные InputEvent и вызывает маршрутизатор ввода.
- Собирает кадр из окон в backbuffer и копирует нужные регионы в screen, затем делает SDL_UpdateWindowSurface*.
- В wasm: вместо бесконечного while — emscripten_set_main_loop.
- Window + WindowVTable — интерфейс каждого окна (draw/on_event/tick/on_focus/destroy).
- WM — список окон, сортировка по z, поиск topmost, фокус по пользователям, общий DamageList, размеры экрана.
- input.c — маршрутизация ввода: выбор целевого окна, перевод координат в локальные, фиксация перетаскивания, постановка damage (включая старый и новый рямоугольник при drag).
- timing.h — FRAME_MS и мелкие хелперы.
- surface.* — тонкая обёртка над SDL_Surface (ARGB32): fill, fill_rect, blit, checkerboard.
- text.* — инициализация TTF, рендер строки в Surface, измерение «ширина/высота».
- apps/ (реальные окна)
- win_paint — фон+рисование по клику/drag.
- win_square — цветной квадрат с анимацией и drag.
- win_console — история строк (кольцевой буфер), редактируемая строка, мигающий курсор.
- Склеивает всё: создаёт платформу → инициализирует текст → создаёт WM → добавляет окна → запускает цикл (native или wasm).
- SDL генерит событие → platform переводит его в InputEvent.
- input_route_*():
- Для мыши: по LMB-down выбирается topmost окно под курсором, делается bring-to-front, фокус для пользователя.
- Перед on_event: сохраняет старый frame.
- Вызывает window->vt->on_event(window, e, lx, ly).
- После on_event: если окно сдвинулось/изменилось, добавляет damage старого и нового прямоугольников, помечает invalid_all=true. Если окно само пометило ебя грязным — добавляет damage его текущей области.
- Для клавиатуры/текста: событие отправляется в сфокусированное окно соответствующего пользователя.
- WM копит DamageList — список прямоугольников, которые надо обновить.
- platform.plat_compose_and_present:
- При необходимости дожидается следующего кадра (≤ 60 Гц).
- Если damage пуст, но есть анимации — берёт весь экран как damage (чтобы реально перерисовать).
- Для каждого damage-региона:
- Очищает фон в backbuffer (черный).
- Идёт по окнам снизу-вверх: % Если invalid_all, вызывает window->vt->draw() (окно обновляет свой кэш w->cache целиком). % Пересекает регион с w->frame и blit-ит соответствующую часть w->cache в backbuffer.
- Копирует готовый регион из backbuffer в screen (SDL_BlitSurface).
- Вызывает SDL_UpdateWindowSurfaceRects (или SDL_UpdateWindowSurface для полноэкрана).
- Очищает DamageList.
Так обеспечиваются:
- частичная перерисовка (только нужные регионы),
- корректная композиция по z-index,
- отсутствие «шлейфов» (старое и новое положение окна попадают в damage).
- Окно, у которого есть активная динамика, ставит w->animating=true и планирует w->next_anim_ms.
wm_tick_animations(now) вызывает w->vt->tick для нужных окон; те помечают себя invalid_all и добавляют damage (обычно область окна), а также переустанавливают next_anim_ms.
- Платформа делает мягкий pacing к FRAME_MS (≈16 мс).
- WM хранит focus[user_id] → Window*.
- Клик левой кнопкой мыши привязывает верхнее окно под курсором к фокусу соответствующего пользователя и поднимает его на передний план.
- Клавиатура/текст идут в текущее окно фокуса. При смене — вызывается on_focus(true/false).
Каждое окно:
- имеет собственный кэш Surface* cache размера frame.w x frame.h (локальные координаты);
- реализует vtable:
- draw(w, invalid) — рисует в свой кэш;
- on_event(w, e, lx, ly) — обрабатывает ввод в локальных координатах;
- tick(w, now) — обновляет состояние и просит следующий кадр;
- destroy(w) — освобождает w->user и ресурсы, специфичные для окна.
- всё специфическое состояние лежит в w->user (malloc-нутый struct), ядро его не знает.
- win_paint: рисует фон (checkerboard) один раз; по клику/drag пишет пиксель в свой cache и помечает invalid_all → ядро перенесёт нужный регион на экран.
- win_square: tick меняет цвет квадрата по косинусному закону, делает invalidate окна каждый кадр анимации; on_event по клику в квадрат разворачивает фазу; drag — меняет frame, но всю грязь и правильный repaint делает централизованный input_route_mouse.
- win_console: хранит историю строк (кольцевой буфер), редактируемую строку, мигает курсором. По Enter — кладёт edit в историю; отрисовывает rows-1 оследних строк + текущую (нижнюю).
- main:
- plat_create → text_init → wm_create.
- Создаёт и добавляет окна.
- Ставит начальный damage всего экрана.
- Цикл:
- plat_poll_events_and_dispatch → wm_tick_animations (если нужно) → plat_compose_and_present.
- Завершение:
- wm_destroy (вызовет destroy у окон и освободит их кэши) → text_shutdown → plat_destroy.
- Все модули видят только свои заголовки (window.h, wm.h, surface.h, text.h).
- SDL-детали заперты в platform/ и gfx/.
- Окна — изолированы: они не знают про SDL и ядро, только про Window, Rect, InputEvent, Surface.
- Менять/добавлять окна можно, не трогая платформу/ядро.
- apps/* не тянут platform/* и не включают SDL-типы.
- core/* не знает про SDL_Window/SDL_Event — только свои абстракции (InputEvent, Rect, Surface*).
- platform/* — единственное место, где живут SDL_Window, события SDL и копирование в окно.
- gfx/* — единственный слой, знающий про SDL_Surface/SDL_ttf; наружу отдаёт Surface*.
- apps/* → core/window.h, core/wm.h, gfx/surface.h, gfx/text.h
- core/* → (внутри core) window.h, wm.h, damage.h, input.h, timing.h + иногда gfx/surface.h
- platform/platform_sdl.* → core/wm.h, core/input.h, core/timing.h, gfx/surface.h + SDL
- gfx/surface.* → SDL (Surface-утилиты)
- gfx/text.* → SDL_ttf (+ SDL), возвращает Surface* (gfx/surface.h)
- main.c → всё публичное: platform/platform_sdl.h, core/wm.h, core/window.h, apps/*, gfx/text.h
- main.c
- platform/platform_sdl.h
- core/wm.h, core/window.h
- apps/win_paint.h, apps/win_square.h, apps/win_console.h
- gfx/text.h
- src/platform/platform_sdl.c
- platform/platform_sdl.h (собственный заголовок)
- core/wm.h, core/input.h, core/timing.h
- gfx/surface.h
- <SDL.h> (и косвенно SDL_ttf через text в рантайме, но прямо не включает)
- src/core/wm.c
- core/wm.h
- gfx/surface.h (для освобождения кэшей окон)
- src/core/input.c
- core/input.h, core/wm.h
- src/core/window.c
- core/window.h
- gfx/surface.h (создание/заливка кэша окна)
- src/core/damage.c
- core/damage.h (inline-реализация в .h)
- src/core/timing.h
- без внешних зависимостей
- src/gfx/surface.c
- gfx/surface.h
- <SDL.h>
- src/gfx/text.c
- gfx/text.h
- <SDL_ttf.h>, <SDL.h>
- gfx/surface.h (создаёт Surface из SDL_Surface)
- src/apps/win_paint.c
- apps/win_paint.h
- gfx/surface.h
- core/wm.h (для invalidate, Rect)
- src/apps/win_square.c
- apps/win_square.h
- gfx/surface.h
- core/wm.h, core/timing.h
- <math.h>
- src/apps/win_console.c
- apps/win_console.h
- gfx/surface.h, gfx/text.h
- core/timing.h
- <SDL.h> *(для SDL_GetTicks), <string.h>, <stdlib.h>
@startuml
title Dependency graph – Cross WM
skinparam packageStyle rectangle
skinparam ArrowColor #888
skinparam packageTitleFontColor #333
skinparam defaultTextAlignment left
package "main" as main {
[main.c]
}
package "platform" as platform {
[platform_sdl.h]
[platform_sdl.c]
}
package "core" as core {
[window.h]
[window.c]
[wm.h]
[wm.c]
[damage.h]
[damage.c]
[input.h]
[input.c]
[timing.h]
}
package "gfx" as gfx {
[surface.h]
[surface.c]
[text.h]
[text.c]
}
package "apps" as apps {
[win_paint.h]
[win_paint.c]
[win_square.h]
[win_square.c]
[win_console.h]
[win_console.c]
}
package "3rd-party" as thirdparty {
[SDL2]
[SDL2_ttf]
}
' ---- Dependencies ----
main --> platform
main --> core
main --> apps
main --> gfx
platform --> core
platform --> gfx
platform --> [SDL2]
core --> gfx
apps --> core
apps --> gfx
gfx --> [SDL2]
gfx --> [SDL2_ttf]
legend right
Arrows show "uses public API of".
core never includes SDL types.
apps know only Window/WM/Input and gfx::Surface/Text.
platform is the only module touching SDL_Window & event loop.
gfx wraps SDL_Surface/SDL_ttf and exposes Surface/Text API.
endlegend
@enduml