Содержание урока · 11 разделов
Возьмите любой вывод fmt.Println из первых уроков и положите рядом с окном редактора в терминале: разница не в содержимом, а в правде о том, что интерфейс — это ещё и вид. Вручную терминал красится управляющими последовательностями вроде ESC[38;5;212m — коды короткие, а код с ними превращается в кашу из заклинаний, которые надо помнить, сбрасывать и тестировать в каждом терминале. Пакет github.com/charmbracelet/lipgloss закрывает это декларативно: стиль — значение типа lipgloss.Style, собирается цепочкой методов и рендерится вызовом Render. Таблица из тринадцатого урока красит выделенную строку стилем Lip Gloss, textarea из четырнадцатого красит курсорную строку — теперь заглянем под капот и соберём свои стили: цвет, начертание, рамка, отступы. К концу урока у вас будет панель, которую не стыдно показать на экране конференции.
lipgloss.NewStyle: цвет и начертание#
Точка входа — lipgloss.NewStyle(): пустой стиль, к которому методы цепляются один за другим. Foreground задаёт цвет текста, Background — фона; цвет — просто строка: число от нуля до 255 означает код палитры ANSI, а строка с решёткой — hex-цвет вроде #FF00FF. Начертания — булевы методы: Bold для жирного, Faint для приглушённого, Italic для курсива, Underline для подчёркивания. Готовый стиль вызывается методом Render: ему отдаёте строку, получаете обратно ту же строку с вкраплёнными управляющими кодами. Важное свойство, на котором держится всё остальное: стиль — неизменяемое значение, каждый метод возвращает новую копию, не трогая исходник. Поэтому в View пишут так: обернули строку — и всё; а в инициализации, если нужно два шага, присваивают результат обратно переменной. Это отличает Lip Gloss от мутабельных конструкторов, к которым приучает обычный Go.
Первый стиль
GOvar titleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("205")).
Bold(true)
var dangerStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF4672")).
Faint(true)
func (m dashboardModel) View() string {
return titleStyle.Render("Панель задач") + "
" +
dangerStyle.Render("5 задач просрочено")
}Два стиля объявлены один раз и переиспользуются из любого места программы — это главная привычка урока. Число 205 — код ANSI-палитры, #FF4672 — hex: Lip Gloss проглотит оба. Render можно звать сколько угодно раз, стиль от этого не портится, потому что методы возвращают копии.
Адаптивные цвета и профиль терминала#
Ваш красивый hex-цвет существует только в терминале, который умеет truecolor. Профиль — число цветов, которые поддерживает текущий терминал — Lip Gloss определяет при старте, и рендер сам деградирует до ближайшего допустимого: в 256-цветном терминале ваш точный оттенок превратится в похожий из палитры, в 16-цветном — ещё грубее. Это не баг, а выживание: та же программа в SSH-сессии на древнем сервере останется читаемой. Вторая реальность — светлая и тёмная темы терминала: серый текст, идеальный на тёмном фоне, на белом исчезает. Для этого есть lipgloss.AdaptiveColor с двумя значениями — Light и Dark: библиотека определяет светлость фона через HasDarkBackground и берёт нужное. Именно так устроен краситель курсорной строки в textarea из прошлого урока. Полный контроль на все профили разом даёт CompleteColor — отдельные цвета для truecolor, 256 и 16, без автоматической деградации, но он нужен редко.
Адаптивный цвет статуса
GOvar statusStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{
Light: "238",
Dark: "252",
})
func (m dashboardModel) statusLine() string {
return statusStyle.Render("обновлено 3 минуты назад")
}На тёмном фоне текст станет светлым (252), на светлом — тёмным (238): читаемость сохраняется без единого if в вашем коде. Значения снова ANSI-коды, hex тоже допустим. Так же строятся второстепенные подписи и подсказки: они не должны кричать, но обязаны быть видимыми в обеих темах.
Проверка: деградация цвета
Стиль с Foreground("#FF00FF") запустили в терминале на 16 цветов. Что увидит пользователь?
Рамки: NormalBorder и RoundedBorder#
Рамка — это не строка дефисов, а готовый набор глифов: у липглосса есть функции NormalBorder с прямыми углами, RoundedBorder со скруглёнными, DoubleBorder с двойными линиями, ThickBorder жирными и ASCIIBorder из плюсов и дефисов — на случай самых простых терминалов. Метод Border принимает такой набор и по умолчанию рисует все четыре стороны; частичный выбор задаётся булевыми аргументами после рамки — по верху, правому краю, низу и левому краю, или отдельными методами вроде BorderTop. Цвет рамки — BorderForeground, и это отдельный цвет от текста: панель с серой рамкой и ярким заголовком выглядит дороже, чем одна сплошная краска. Рамка ложится после отрисовки текста и паддингов, поэтому геометрия считается честно: ширина панели складывается из текста, отступов и двух боковых линий. В таблице ниже — самые ходовые наборы, чтобы выбрать на глаз.
Панель с рамкой и отступами
GOvar panelStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62")).
Padding(1, 2)
func (m dashboardModel) summary() string {
return panelStyle.Render("Всего задач: 12
Готово: 7")
}RoundedBorder даёт скруглённые углы, BorderForeground красит только рамку, не трогая текст. Padding(1, 2) добавляет по пустой строке сверху и снизу и по два пробела по бокам — многострочный текст в Render разбирается честно, каждая строка получит свою рамку. Фиолетовый код 62 — классика примеров Charm.
Наборы рамок#
| Функция | Вид углов | Когда брать |
|---|---|---|
| NormalBorder() | прямые | дефолт, деловые панели |
| RoundedBorder() | скруглённые | современный вид, дашборды |
| DoubleBorder() | двойные | акцентные окна, диалоги |
| ThickBorder() | жирные | крупные заголовки, баннеры |
| ASCIIBorder() | плюсы и дефисы | максимальная совместимость |
| HiddenBorder() | невидимые | отступы без линий |
Padding и Margin: отступы внутри и снаружи#
Два способа отодвинуть текст — и они не взаимозаменяемы. Padding — внутренние отступы: воздух между текстом и рамкой, и фон блока заливает и их тоже, поэтому цветная плашка с Padding выглядит цельной, а не как текст с прилипшей рамкой. Margin — внешние: пустое место вокруг уже готового блока, которым панели отодвигаются друг от друга; фон сюда по умолчанию не красится, но есть MarginBackground, если нужна сплошная заливка. Порядок аргументов у обоих методов CSS-образный: один аргумент — все стороны, два — вертикаль и горизонталь, четыре — по часовой от верха. Для одной стороны есть отдельные PaddingLeft, MarginTop и соседи. Держите в голове последовательность рендера: текст, затем паддинги, затем рамка, затем маргины — отсюда следует, что Width задаёт ширину содержимого, а рамка и маргины дорисуются сверху. Геттеры вроде GetHorizontalFrameSize вернут, сколько места рамка и отступы уже съели.
Проверка: порядок аргументов
Сколько пробелов отступит Padding(1, 2) слева и справа?
Размеры вывода
Прочитайте код и выберите, что он выведет:
s := lipgloss.NewStyle().
Border(lipgloss.NormalBorder()).
Padding(0, 1)
out := s.Render("ok")
// Сколько строк в out и какова ширина самой широкой строки?Наследование: Copy() и Inherit#
Стиль-база и производные от него — классический паттерн библиотеки: вы описываете общий вид один раз, а вариации получают точечные отличия. Исторически для этого существовал метод Copy: errStyle := base.Copy().Foreground(red) — база оставалась нетронутой, копия красилась. Здесь стоит быть честным до конца: в версиях Lip Gloss 1.1 и новее Copy помечен устаревшим, потому что методы и так возвращают новые значения, и то же самое записывается как base.Foreground(red) — присваиванием в новую переменную. В старых примерах, документации и чужом коде Copy встречается сплошь и рядом: читать его надо спокойно, а в новом коде я всё же оставляю вызов ради намерения — строка с Copy мгновенно читается как «делаем вариацию», хотя линтер вправе предложить иначе. Второй инструмент — Inherit: он переносит на стиль только те свойства базового, что явно установлены, и не трогает паддинги с маргинами. Это удобно для тем: одна база задаёт цвета, наследники добавляют свои отступы.
База и производные стили
GOvar baseStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62")).
Padding(1, 2)
var okStyle = baseStyle.Copy().
BorderForeground(lipgloss.Color("42"))
var errStyle = baseStyle.Copy().
BorderForeground(lipgloss.Color("#FF4672")).
Bold(true)База держит форму: рамку и отступы. Вариации правят только цвет рамки, errStyle заодно жирнит текст. Base после вызовов Copy остаётся прежним — каждое звено цепочки возвращает новый стиль. На версиях 1.1 и выше те же строки пишутся без Copy, результат идентичен.
В двенадцатом уроке обещали: у полосы прогресса нет собственной рамки. Лечится обёрткой — m.progress.View() отдайте стилю с Border и Padding(0, 1), и полоса получит аккуратное окно. Только помните про пружину: во время анимации длина строки не меняется, рамка не будет дёргаться.
Ширина, высота и геттеры#
Методы Width и Height на стиле — это уже не краска, а геометрия: Width задаёт ширину блока в символах, и если текст длиннее, он переносится по словам, а короткие строки добираются пробелами до одной длины. Height добивает блок пустыми строками сверху или снизу — вертикальное выравнивание регулируется AlignVertical. Это уже половина раскладки, и вторая её половина — соединение блоков — тема следующего урока. Здесь зафиксируйте факт: блок с Width — прямоугольник со строками одной длины, и именно поэтому его можно предсказуемо склеивать, печатать и обкладывать рамками. Для отладки и расчётов у стиля есть геттеры — GetWidth, GetForeground, GetBold: они возвращают то, что реально установлено, и спасают, когда стили собираются из нескольких баз по условиям.
Соберите стиль панели
Восстановите конструктор стиля: создание, цвет текста, рамка и её цвет уже на месте.
Соберите наследование стиля
Расставьте строки: база, вариация, рамка, отступы, рендер, печать.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: словарь Lip Gloss
Что дальше#
Красить блоки вы умеете — пора их соединять: экран собирается из панелей, и урок про раскладку научит склеивать блоки бок о бок, выравнивать и центрировать, а заодно считать ширину по сообщению WindowSizeMsg. Там же пригодится и эта база: все примеры раскладки — это стили из сегодняшнего урока. Тему целиком — наборы цветов, переключение светлое/тёмное, — интересно сравнить с десктопом: у нас есть урок про темы в Fyne, где решается та же задача, но с пикселями и шрифтами. Стили из урока лягут в отдельный файл проекта — об организации кода есть урок про структуру большого TUI-проекта. Практика: соберите три стиля — заголовок, успех и ошибку — от одной базы, оберните в рамку строку статуса с адаптивным цветом и посмотрите на неё в светлой и тёмной теме терминала.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Виртуальный терминал
модель Bubble Tea · Model → Update → ViewОтредактируйте модель, Update или View — и нажмите «Собрать модель»: код разберётся и начнёт отвечать на клавиши, как настоящий tea.Program.
Клавиатура — обработано в Update
модель собрана: alerts bool = false · done int = 7 · total int = 12
вне эмуляции: рамки lipgloss (border)
Коротко: что запомнить
- lipgloss.NewStyle плюс цепочка методов: Foreground/Background, Bold/Faint/Italic, Render в конце.
- Цвет — строка: ANSI 0–255 или hex; профиль терминала определяется сам, цвет деградирует честно.
- AdaptiveColor выбирает между Light и Dark по светлости фона — читаемость без if.
- Рамки — готовые наборы глифов: Normal, Rounded, Double, Thick; цвет рамки — BorderForeground.
- Padding — внутри блока и красится фоном, Margin — снаружи; порядок аргументов как в CSS.
- Стили неизменяемы: методы возвращают копию; Copy() — классика наследования, с 1.1 устарел.
Домашнее задание
Соберите мини-панель статуса: базовый стиль с рамкой RoundedBorder и отступами, от него три вариации — обычная, успешная с зелёной рамкой и ошибочная с красной и жирным текстом. Внутрь положите адаптивную подпись, которая читается в обеих темах. Проверьте в терминале на 256 цветах и в truecolor: деградация должна остаться читаемой.
Личные заметкиПусто
Вопросы и ответы по уроку
Цвета пропали, когда вывод программы ушёл в файл или пайп. Это сломалось?
Нет: при выводе не в терминал профиль определяется как без цветов, и Lip Gloss честно рендерит чистый текст. Это правильное поведение — в логах не должно быть управляющих кодов. Если нужен цвет принудительно, у рендера можно выставить профиль вручную, но делать это стоит осознанно.
Нужно ли писать Copy() при выводе вариации стиля?
С версии 1.1 — нет: методы имеют приёмник-значение и возвращают новый стиль, поэтому errStyle := base.Foreground(red) корректен и без Copy. Вызов Copy остаётся валидным, просто помечен устаревшим. В кодовой базе до 1.1 без Copy не обойтись: там методы меняли исходный стиль, и без копии база мутировала бы вместе с каждым наследником.
Почему фон не заливает область вокруг рамки, хотя Padding есть?
Потому что Margin добавляется после рамки и фоном не красится — это пустое место между блоками. Если нужна сплошная цветная плашка с воздухом, используйте MarginBackground для заливки маргинов или увеличьте Padding: внутренние отступы всегда красятся вместе с блоком.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Zap: структурное логирование на Go
Интерактивный курс Zap — быстрого логгера для Go: уровни, поля, JSON, ротация, интеграции с Gin, GORM и gRPC. 25 уроков с примерами и итоговым проектом.
- Писать структурные логи с полями вместо строк
- Выбирать между Logger и SugaredLogger осознанно
- Настраивать энкодеры, выводы и ротацию файлов
- Разбираться в zapcore: Core, Encoder, WriteSyncer, LevelEnabler