Документ описывает внешний вид системы: из чего он собран, какими числами задан и где в коде что лежит. Он нужен, чтобы правка одного окна не разошлась с остальными. Снимки того, что из этого получилось, — в docs/SHOTS.md.
До этой работы стол был нарисован растровым шрифтом 8×8, растянутым в два раза, прямыми углами и сплошными заливками. Это не было стилем — это было отсутствие выбора: другого способа нарисовать букву у системы не существовало. Плата видна на любом снимке: все буквы одной ширины, ступеньки на каждой наклонной, разница между заголовком и подписью — только в цвете.
Теперь есть три вещи, которых не было:
- Настоящий шрифт со сглаживанием. Inter для интерфейса, JetBrains Mono для
всего, что выравнивается по столбцам. Оба под SIL OFL, оба лежат в
tools/font/ttf/. - Сглаженные скругления, градиенты и прозрачность. Скруглённый прямоугольник считается по знаковому расстоянию до фигуры, а не рисуется четвертями окружности по Брезенхэму.
- Две темы. Тёмная и светлая, одна геометрия на обе; переключаются правым щелчком по столу и в «Параметрах».
| файл | что в нём |
|---|---|
tools/font/genfont.py |
растеризация шрифтов в таблицы; запускается руками |
crates/mini-ui/src/typeface.rs |
начертания, измерение и вывод текста |
crates/mini-ui/src/typeface/data.rs |
сгенерированные таблицы глифов |
crates/mini-ui/src/draw.rs |
примитивы: скругления, градиенты, обводки, тени |
crates/mini-ui/src/glyphicon.rs |
штриховые значки, нарисованные кодом |
crates/kernel/src/ui/theme.rs |
палитра обеих тем и все размеры |
crates/kernel/src/ui/paint.rs |
словарь элементов: кнопки, карточки, строки |
crates/kernel/src/ui/window.rs |
заголовок окна и кнопки управления |
crates/kernel/src/ui/compositor.rs |
обои, тени, срез углов при выводе |
crates/kernel/src/ui/panel.rs |
панель задач и меню запуска |
crates/kernel/src/ui/settings.rs |
окно «Параметры», в том числе выбор темы |
crates/kernel/src/ui/files.rs |
файловый менеджер |
crates/kernel/src/ui/icons.rs |
значки на столе |
crates/kernel/src/ui/context.rs |
меню по правому щелчку |
Растеризация идёт на машине разработчика, а не в ядре. Разбор TrueType внутри ядра означал бы парсер бинарного формата, растеризатор кривых Безье и кэш глифов — три подсистемы со своими переполнениями ради задачи, ответ на которую известен заранее. Поэтому глифы растеризованы заранее через FreeType, и в ядро попадает таблица полутоновых картинок: 168 КиБ на все восемнадцать начертаний, по четыре бита на точку.
Шрифт не масштабируется: растровый глиф, растянутый вдвое, — это ровно те ступеньки, ради избавления от которых всё делалось. Вместо этого есть два размерных ряда, и каждый растеризован отдельно. Ряд выбирается по ширине экрана один раз при запуске: от 2560 точек — крупный, иначе обычный.
Начертание выбирается ролью, а не кеглем: кегль зависит от экрана, а назначение — нет.
| роль | обычный ряд | крупный ряд | где |
|---|---|---|---|
Caption |
11 | 15 | подписи, мелкий текст |
Body |
13 | 18 | обычный текст, пункты меню |
Label |
12 | 16 | подписи значков, вкладки |
Title |
13 | 18 | заголовок окна, выделенная строка |
Strong |
14 | 19 | заголовок карточки |
Heading |
20 | 27 | заголовок раздела |
Mono |
12 | 16 | пути, числа, терминал |
MonoSmall |
11 | 14 | технические подписи |
MonoCaps |
10 | 13 | ЗАГОЛОВКИ СЕКЦИЙ, с разрядкой |
Чтобы поменять набор знаков или список начертаний, правят
tools/font/genfont.py и запускают:
python tools/font/genfont.py
Файл data.rs руками не правят — следующий запуск генератора всё перепишет.
Цвета задаются вместе с прозрачностью, ровно так, как записаны в макете: окно — шестьдесят процентов над обоями, панель — пятьдесят два над окном, линия — семь процентов белого. Это не украшение: именно из-за прозрачности вложенные поверхности отличаются друг от друга на несколько единиц яркости, а границы между ними держатся на однопиксельных линиях, а не на разнице заливок.
Поверхность в памяти прозрачности не хранит, поэтому цвета сводятся к непрозрачным поверх той подложки, на которой окажутся. Слои, лежащие прямо на обоях — панель задач, меню запуска, подсветка значка, — не сводятся, а смешиваются по-настоящему: обои считаются по формуле, и цвет под любой точкой известен.
Тем ровно две. Третья не отвечает ни на один вопрос, на который не отвечают эти две, а стоит столько же, сколько первые две вместе: каждый оттенок надо подобрать заново и проверить на всех поверхностях.
Все числа — в theme.rs, и все умножаются на множитель геометрии
(geometry_scale): 1 на обычном экране, 2 от 2560 точек.
| элемент | значение |
|---|---|
| скругление окна | 14 |
| скругление панели, меню, карточки | 12 |
| скругление строки, кнопки, поля | 10 |
| скругление мелкой кнопки | 9 |
| скругление вкладки | 7 |
| заголовок окна | 44 |
| кнопки заголовка | 30×30, зазор 6, отступ от края 10 |
| панель инструментов | 48 |
| строка состояния | 30 |
| строка бокового списка | 34 |
| боковая колонка | 240 |
| панель задач | 52, отступ от краёв 14 |
| строка меню запуска | 36 |
| плитка значка | 46×46, скругление 13 |
| ячейка значка | 110×100, зазор 12, отступ сетки 28 |
Толщина обводки везде одна точка, внутрь границы.
Высота заголовка не зависит от фокуса, хотя в макете у неактивного окна полоса ниже. Иначе при каждом переключении окон область содержимого меняла бы размер, и список файлов пересобирал бы раскладку. Неактивное окно отличается тем, что теряет градиент, светлую кромку и заливку кнопок.
Поверхность окна прямоугольна, и прозрачности в ней нет. Скруглённый угол — это место, где сквозь окно видны обои, а обои знает только композитор. Поэтому окно рисует себя во всю поверхность, а углы срезаются при выводе, смешиванием с уже нарисованным фоном. Строки, проходящие мимо углов, копируются целиком: срез касается четырёх квадратов со стороной в радиус, то есть восьмисот точек на окно любого размера.
Считаются, а не хранятся картинкой: картинка размером с экран — четыре мегабайта при куче в шестнадцать. Цвет любой точки — функция от её координат.
Состав: эллиптическое световое пятно у верхнего края, два размытых цветных круга по противоположным углам и редкая сетка точек с шагом 24. Пятна и сетка не украшение — на однородной заливке перетаскиваемое окно кажется стоящим на месте, и глазу нужна опора, чтобы увидеть движение.
У кругов квадратичное затухание, а не настоящее размытие: размытие — это свёртка,
то есть проход по окрестности каждой точки, а затухание (1 - d²)² даёт от
центра к краю ту же дугу за два умножения.
Тема — глобальный признак в theme.rs. Меняется двумя дорогами:
- правым щелчком по столу — пункт «Светлая тема» / «Тёмная тема»;
- в «Параметрах», раздел «Экран», щелчком по образцу.
Пункт в меню стола есть по той же причине, по которой обои везде меняют щелчком по столу: это свойство самого стола, и идти за ним в окно настроек человек не должен. Он же называется тем, что произойдёт, а не тем, что есть сейчас: «Тёмная тема» при включённой тёмной читалась бы как признак, а не как действие.
Из «Параметров» окно сообщает о смене наружу через Window::took_theme_change.
В обоих случаях перекрашивает стол Compositor::restyle — целиком и сразу.
Оставить хоть один слой в прежней палитре значит показать наполовину
перекрашенный стол, который человек примет за поломку.
Обе дороги проверяются прогоном: сценарий mouse переключает тему пунктом меню
и снимает стол в обеих палитрах (12-light, 13-dark-again) при открытом окне,
панели и значках — то есть ровно то, что ломается, если перекрасить не всех.
Поверхности при этом не пересоздаются: размеры от темы не зависят. Напечатанное в терминале сохраняется — сетка перерисовывается из теневого буфера, который она и так ведёт ради прокрутки.
У программы два имени. App::title — латиницей, попадает в журнал; по нему стенд
находит окно на экране и сверяет строки прогона. App::caption — по-русски, это
то, что видит человек в заголовке, в панели задач и в меню.
Разделение не косметическое: журнал — инструмент разработчика, и сценарии стенда написаны против него; заголовок окна читает человек, и он на его языке.
Числа геометрии живут в одном месте — theme.rs, — и по ним же стенд наводит
указатель (xtask/src/harness/aim.rs). Разойдись они, прицел молча попадает в
кнопку соседа, и «свернул вместо закрыл» выглядит как ошибка системы, а не
стенда. Меняя размер, меняют оба файла.