Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 15 из 25 ~20 мин +50 XP

Lip Gloss: стили, цвета и рамки

Lip Gloss — go-библиотека из набора Charm, которая заменяет ручные ANSI-коды в стилях терминала на цепочки методов: цвет, начертание, рамку и отступы задаёт один вызов NewStyle. Всё, что вы красили до сих пор руками — от выделенной строки таблицы до курсорной строки редактора, — живёт здесь. Разбираем цвета, адаптацию к палитре терминала, рамки и отступы.

Содержание урока · 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.

Первый стиль

GO
var 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, без автоматической деградации, но он нужен редко.

Адаптивный цвет статуса

GO
var 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, и это отдельный цвет от текста: панель с серой рамкой и ярким заголовком выглядит дороже, чем одна сплошная краска. Рамка ложится после отрисовки текста и паддингов, поэтому геометрия считается честно: ширина панели складывается из текста, отступов и двух боковых линий. В таблице ниже — самые ходовые наборы, чтобы выбрать на глаз.

Панель с рамкой и отступами

GO
var 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: он переносит на стиль только те свойства базового, что явно установлены, и не трогает паддинги с маргинами. Это удобно для тем: одна база задаёт цвета, наследники добавляют свои отступы.

База и производные стили

GO
var 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, результат идентичен.

Ширина, высота и геттеры#

Методы Width и Height на стиле — это уже не краска, а геометрия: Width задаёт ширину блока в символах, и если текст длиннее, он переносится по словам, а короткие строки добираются пробелами до одной длины. Height добивает блок пустыми строками сверху или снизу — вертикальное выравнивание регулируется AlignVertical. Это уже половина раскладки, и вторая её половина — соединение блоков — тема следующего урока. Здесь зафиксируйте факт: блок с Width — прямоугольник со строками одной длины, и именно поэтому его можно предсказуемо склеивать, печатать и обкладывать рамками. Для отладки и расчётов у стиля есть геттеры — GetWidth, GetForeground, GetBold: они возвращают то, что реально установлено, и спасают, когда стили собираются из нескольких баз по условиям.

Соберите стиль панели

Восстановите конструктор стиля: создание, цвет текста, рамка и её цвет уже на месте.

var panelStyle = lipgloss.(). Foreground(lipgloss.("205")). Border(lipgloss.()). BorderForeground(lipgloss.Color("62")). Padding(1, 2)

Соберите наследование стиля

Расставьте строки: база, вариация, рамка, отступы, рендер, печать.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: словарь Lip Gloss

Термин

lipgloss.Color

нажмите, чтобы перевернуть
Определение

Цвет строкой: число 0–255 — палитра ANSI, строка с # — hex. При нехватке цветов терминала деградирует до ближайшего оттенка.

1 / 6

Что дальше#

Красить блоки вы умеете — пора их соединять: экран собирается из панелей, и урок про раскладку научит склеивать блоки бок о бок, выравнивать и центрировать, а заодно считать ширину по сообщению WindowSizeMsg. Там же пригодится и эта база: все примеры раскладки — это стили из сегодняшнего урока. Тему целиком — наборы цветов, переключение светлое/тёмное, — интересно сравнить с десктопом: у нас есть урок про темы в Fyne, где решается та же задача, но с пикселями и шрифтами. Стили из урока лягут в отдельный файл проекта — об организации кода есть урок про структуру большого TUI-проекта. Практика: соберите три стиля — заголовок, успех и ошибку — от одной базы, оберните в рамку строку статуса с адаптивным цветом и посмотрите на неё в светлой и тёмной теме терминала.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Виртуальный терминал

модель Bubble Tea · Model → Update → View

Отредактируйте модель, Update или View — и нажмите «Собрать модель»: код разберётся и начнёт отвечать на клавиши, как настоящий tea.Program.

виртуальный терминал · bubbleteaalt-screen
ПАНЕЛЬ ЗАДАЧ
Всего: 12 · готово: 7
всё под контролем
d — тревога, +/- — готово, q — выход

Клавиатура — обработано в 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: деградация должна остаться читаемой.

Личные заметкиПусто
Хранятся только в вашем браузере (Bubble Tea · Lip Gloss: стили, цвета и рамки)

Вопросы и ответы по уроку

Цвета пропали, когда вывод программы ушёл в файл или пайп. Это сломалось?

Нет: при выводе не в терминал профиль определяется как без цветов, и Lip Gloss честно рендерит чистый текст. Это правильное поведение — в логах не должно быть управляющих кодов. Если нужен цвет принудительно, у рендера можно выставить профиль вручную, но делать это стоит осознанно.

Нужно ли писать Copy() при выводе вариации стиля?

С версии 1.1 — нет: методы имеют приёмник-значение и возвращают новый стиль, поэтому errStyle := base.Foreground(red) корректен и без Copy. Вызов Copy остаётся валидным, просто помечен устаревшим. В кодовой базе до 1.1 без Copy не обойтись: там методы меняли исходный стиль, и без копии база мутировала бы вместе с каждым наследником.

Почему фон не заливает область вокруг рамки, хотя Padding есть?

Потому что Margin добавляется после рамки и фоном не красится — это пустое место между блоками. Если нужна сплошная цветная плашка с воздухом, используйте MarginBackground для заливки маргинов или увеличьте Padding: внутренние отступы всегда красятся вместе с блоком.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Zap25 уроков~9 чпрактика25 уроков уже доступны

Zap: структурное логирование на Go

Интерактивный курс Zap — быстрого логгера для Go: уровни, поля, JSON, ротация, интеграции с Gin, GORM и gRPC. 25 уроков с примерами и итоговым проектом.

  • Писать структурные логи с полями вместо строк
  • Выбирать между Logger и SugaredLogger осознанно
  • Настраивать энкодеры, выводы и ротацию файлов
  • Разбираться в zapcore: Core, Encoder, WriteSyncer, LevelEnabler