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

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

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

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

Canvas: примитивы рисования

Пора открыть нижний этаж тулкита: пакет canvas, из примитивов которого собраны все виджеты, — так было сказано ещё в первом уроке. Запрос «fyne canvas» — это про Rectangle, Circle, Line, Text, Image и Raster: цвета, прозрачность, FillMode картинок и правило Refresh. Рисование fyne начинается здесь, а заканчивается в восьмом уроке собственным виджетом. Каркас по-прежнему тот же: app.New, окно — и вперёд.

Содержание урока · 11 разделов

Шесть уроков ты собирал интерфейс из готовых виджетов — и этого хватало. Сегодня спускаемся на уровень ниже, к материалу, из которого сделаны сами виджеты: пакет canvas и его примитивы. Это не декоративная тема: внешний вид любой кнопки — это WidgetRenderer, собранный из тех же прямоугольников и текста, а индикаторы, диаграммы, шкалы и миниатюры в реальных приложениях почти всегда рисуются примитивами вручную. В этом уроке разберём семь примитивов — Rectangle, Circle, Line, Text, Image, Raster и LinearGradient, — научимся красить их цветами и прозрачностью, укрощать размеры и не забывать Refresh. Всё это станет фундаментом восьмого урока, где ты соберёшь собственный виджет; а ещё это ответ на любой запрос вида «нарисовать график в fyne» — примитивы плюс математика, больше ничего не нужно.

Rectangle: квадратное начало#

canvas.Rectangle — самый частый примитив: подложки панелей, плашки статусов, цветные полосы. У него три краски: FillColor заливает фигуру, StrokeColor со StrokeWidth рисуют контур. Создаётся либо функцией canvas.NewRectangle(color), либо литералом &canvas.Rectangle{FillColor: ...} — разницы нет, выбирай что читабельнее. Первое, что надо понять: примитив не знает, какого размера ему быть. У Rectangle, как и у большинства объектов пакета, минимальный размер нулевой — размеры диктует контейнер-владелец или прямой вызов Resize. Положишь прямоугольник в VBox — получишь полосу нулевой высоты; отдашь ячейке грида — он растянется на всю ячейку. Второе железное правило: поменяй поле — позови Refresh(). Цвет, записанный в FillColor, не сам просится на экран: перерисовка происходит по команде, и без Refresh новое значение проявится только когда окно перерисуется само, например при изменении размера. Так устроен весь fyne canvas: примитив — данные, Refresh — приказ их нарисовать. Третье: цвета задаются из пакета image/color стандартной библиотеки — обычно color.NRGBA с четырьмя каналами, о палитрах чуть позже.

Лампа, которую красит кнопка

GO
package main

import (
        "image/color"

        "fyne.io/fyne/v2"
        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/canvas"
        "fyne.io/fyne/v2/container"
        "fyne.io/fyne/v2/widget"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Светофор")

        lamp := canvas.NewRectangle(
                color.NRGBA{R: 231, G: 76, B: 60, A: 255},
        )

        green := false
        switcher := widget.NewButton("Переключить", func() {
                green = !green
                if green {
                        lamp.FillColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255}
                } else {
                        lamp.FillColor = color.NRGBA{R: 231, G: 76, B: 60, A: 255}
                }
                lamp.Refresh()
        })

        window.SetContent(container.NewVBox(
                container.NewGridWrap(fyne.NewSize(160, 48), lamp),
                switcher,
        ))
        window.ShowAndRun()
}

GridWrap из пятого урока даёт прямоугольнику точный размер 160 на 48 — сам по себе примитив нулевого минимального размера. Кнопка меняет поле FillColor и зовёт Refresh(): без него цвет обновился бы только при следующей перерисовке окна. color.NRGBA — цвет с альфа-каналом; 255 означает полную непрозрачность. Логика состояния зелёный/красный живёт в булевой переменной замыкания.

Circle и Line: точки и черты#

canvas.Circle — окружность, вписанная в свой прямоугольник: те же FillColor, StrokeColor и StrokeWidth. В ячейке с равными сторонами получится круг, в вытянутой — эллипс: примитив честно заполняет отведённое место, за пропорциями следишь ты — здесь снова пригодится GridWrap из пятого урока. canvas.Line устроен неожиданно: у него нет полей вида «от точки до точки» — линия рисуется как диагональ своего прямоугольника. Позиция объекта задаёт начало, позиция плюс размер — конец; направь размер строго горизонтально — получишь черту-разделитель, дай высоту — наклонную линию, а набор коротких отрезков складывается в график. Толщина — StrokeWidth, заливки у линии нет, только StrokeColor. Это модель «ограничивающего прямоугольника», и она сначала сбивает с толку, но потом окупается: линия подчиняется тем же компоновкам, что и всё остальное, растёт и сжимается вместе с контейнером без пересчёта координат. Хочешь разделитель на всю ширину панели — положи Line в грид или в Border, и он растянется сам. Полоску нулевой высоты в VBox, как и прямоугольник, не увидишь: размеры примитивам всегда выдаёт контейнер.

Как canvas.Line понимает, откуда и куда рисовать?

Почему прямоугольника не видно?

Прочитайте код и выберите, что он выведет:

window.SetContent(container.NewVBox(
        canvas.NewRectangle(color.NRGBA{R: 255, G: 0, B: 0, A: 255}),
        widget.NewButton("Нажми", nil),
))

canvas.Text: текст как примитив#

canvas.Text — надпись без обёртки виджета: поле Text хранит строку, Color — цвет, Alignment задаёт выравнивание константами fyne.TextAlignLeading, fyne.TextAlignCenter и fyne.TextAlignTrailing, TextStyle включает Bold, Italic и Monospace, а TextSize — размер в пунктах. Функция canvas.NewText(s, color) делает то же в одну строку. В отличие от виджетов, у Text нет темы: какой цвет назначил — такой и есть, и смена темы его не перекрасит, пока ты не назначишь новый и не позовёшь Refresh. Чем это отличается от widget.Label из третьего урока? Label — виджет: берёт цвет и шрифт из активной темы, перерисовывается сам при её смене и участвует в стандартных механизмах вроде масштабирования шрифтов; canvas.Text — сырое рисование: полный контроль над цветом, размером и стилем в обмен на ручное обновление. Редактировать текст он тоже не умеет: для ввода нужен Entry из третьего урока, подробный разбор поля ввода — девятый. Практическая ниша Text — надписи поверх рисунка: штампы на превью, подписи осей, значения на шкалах, watermark на миниатюре.

Штамп и линия-разделитель

GO
package main

import (
        "image/color"

        "fyne.io/fyne/v2"
        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/canvas"
        "fyne.io/fyne/v2/container"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Штамп")

        stamp := canvas.NewText("ОТГРУЖЕНО",
                color.NRGBA{R: 46, G: 204, B: 113, A: 255})
        stamp.TextStyle = fyne.TextStyle{Bold: true}
        stamp.Alignment = fyne.TextAlignTrailing

        rule := canvas.NewLine(color.NRGBA{R: 120, G: 120, B: 120, A: 255})
        rule.StrokeWidth = 2

        window.SetContent(container.NewVBox(
                container.NewGridWrap(fyne.NewSize(240, 32), stamp),
                container.NewGridWrap(fyne.NewSize(240, 2), rule),
        ))
        window.ShowAndRun()
}

Две плитки GridWrap выдают примитивам точные размеры: штамп 240 на 32 выравнивается по правому краю, линия 240 на 2 растянулась почти горизонтально — она же диагональ своего прямоугольника, сделай плитку 240 на 20 и получишь наклон. Без GridWrap оба примитива в VBox имели бы нулевую высоту — это та же ловушка, что и с прямоугольником.

Каталог примитивов пакета canvas#

ПримитивКлючевые поляТипичная роль
canvas.RectangleFillColor, StrokeColor, StrokeWidthподложки, плашки, индикаторы
canvas.CircleFillColor, StrokeColor, StrokeWidthточки, лампочки, маркеры
canvas.LineStrokeColor, StrokeWidthразделители, простые графики
canvas.TextText, Color, Alignment, TextStyleнадписи поверх рисунка, штампы
canvas.ImageFile, Resource, FillMode, Translucencyфото, иконки, заставки
canvas.RasterGenerator func(w, h int) image.Imageпиксельные эффекты, визуализации
canvas.LinearGradientStartColor, EndColor, Angleфоны и плашки с градиентом

Чем canvas.Text отличается от widget.Label?

Image: три способа загрузить и FillMode#

canvas.Image показывает растровые картинки, и у него три источника. Поле File — путь к файлу на диске, для него есть canvas.NewImageFromFile; поле Resource — fyne.Resource, данные, вшитые в бинарник, — canvas.NewImageFromResource, о ресурсах и embed подробно расскажет шестнадцатый урок; поле Image — готовый image.Image из памяти, например отрисованное другим кодом, — canvas.NewImageFromImage. Главный настрой — FillMode, режим вписывания. ImageFillStretch растягивает картинку на весь контейнер без сохранения пропорций: заполнит любую ячейку, но круг превратит в овал — это значение по умолчанию. ImageFillContain вписывает с сохранением пропорций: если контейнер выше, появятся пустые поля сверху и снизу. ImageFillOriginal рисует в натуральном размере без всякого масштабирования: картинка 1200 на 800 будет именно такой, даже если окно 300 на 200, — удобно с прокруткой, а в маленьком контейнере она просто уйдёт за края. Плюс Translucency — прозрачность от 0 до 1, чтобы призрачно наслаивать картинки. Частая ошибка новичка — ожидание, что Image сам уменьшится: он делает ровно то, что сказал FillMode, и не пикселем больше.

Что покажет ImageFillOriginal?

Прочитайте код и выберите, что он выведет:

img := canvas.NewImageFromFile("кот.png")
img.FillMode = canvas.ImageFillOriginal
// файл кот.png имеет размер 1200 x 800
window.SetContent(container.NewScroll(img))
// окно открыто на 400 x 300

Raster и LinearGradient: пиксели и плавность#

Когда примитивов мало, включается canvas.Raster: ты отдаёшь функцию-генератор func(w, h int) image.Image, и при каждой перерисовке она получает размер области в пикселях и возвращает картинку. Полный контроль над каждым пикселем: визуализации, тепловые карты, фракталы, эффекты пламени — всё это Raster. Плата за свободу — собственные руки: у примитивов сглаживание делает painter тулкита, а в генераторе ты рисуешь в буфер image.Image сам, и ступеньки убираешь своими силами. Для простых плавных переходов есть готовый и дешёвый canvas.LinearGradient: поля StartColor, EndColor и Angle дают градиентную подложку любой плашки без единого пикселя вручную — чаще всего это именно то, что нужно, а Raster оставь попиксельным задачам. Важная деталь производительности: генератор Raster вызывается заново при каждой перерисовке области, поэтому тяжёлую математику кешируй — считай один раз и отдавай готовый image.Image, если параметры не изменились. Эта же гигиена пригодится в восьмом уроке: рендерер виджета тоже перерисовывается неоднократно.

Горизонтальный градиент через Raster

GO
package main

import (
        "image"
        "image/color"

        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/canvas"
        "fyne.io/fyne/v2/container"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Градиент")

        fade := canvas.NewRaster(func(w, h int) image.Image {
                img := image.NewNRGBA(image.Rect(0, 0, w, h))
                for x := 0; x < w; x++ {
                        v := uint8(255 * x / max(w-1, 1))
                        for y := 0; y < h; y++ {
                                img.SetNRGBA(x, y,
                                        color.NRGBA{R: v, G: v, B: 255, A: 255})
                        }
                }
                return img
        })

        window.SetContent(container.NewStack(fade))
        window.ShowAndRun()
}

Генератор получает ширину и высоту области в пикселях и собирает image.NewNRGBA столбец за столбцом: яркость синего растёт слева направо. container.NewStack растягивает Raster на всё окно — стек-компоновка подгоняет детей под свой размер. Для простого перехода хватило бы canvas.LinearGradient с StartColor и EndColor; Raster здесь ради демонстрации попиксельного контроля.

Поменяли lamp.FillColor, но экран не изменился. Что забыли?

Покраска: цвета, прозрачность и тема#

Все примитивы красятся одним типом — color.Color из стандартной библиотеки, и на практике это почти всегда color.NRGBA{R, G, B, A}: четыре канала от 0 до 255, где альфа задаёт прозрачность — 0 полностью прозрачно, 255 непрозрачно. Полупрозрачные плашки поверх фото делаются одним значением альфы, например A: 160. Второй источник цветов — активная тема: theme.Primary(), theme.Error(), theme.Warning() и другие возвращают color.Color текущей темы, и подложки, совпадающие с палитрой приложения, лучше брать оттуда, а не изобретать оттенки. Но есть нюанс, о котором часто спотыкаются: ты присваиваешь полю примитива значение цвета на момент присваивания. Если пользователь сменит тему, виджеты перекрасятся сами — их рендереры слушают тему, а твой Rectangle останется в старом цвете, пока ты не присвоишь новое значение из обновлённой темы и не позовёшь Refresh. Это плата за сырость примитивов: полный контроль и ручное обслуживание. Как строить свои темы и корректно перекрашивать всё приложение — пятнадцатый урок; здесь запомни правило: примитив запоминает цвет, а не подписку на него. Так начинается осмысленное рисование fyne: палитра — твоя забота.

Перекрась лампу

Восстанови четыре пропуска: конструктор примитива, альфа-канал, поле цвета и команду перерисовки.

lamp := canvas.(color.NRGBA{R: 52, G: 152, B: 219, A: }) lamp. = color.NRGBA{R: 241, G: 196, B: 15, A: 255} lamp.() // перерисовать немедленно

Путь одного кадра

Расставь по порядку, что происходит от смены цвета до появления его на экране.

Строки

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

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

Что дальше: примитивы внутри своего виджета#

Примитивы у тебя в руках, и это не случайность: внешний вид любого виджета — это WidgetRenderer, собранный из тех же canvas-объектов. Восьмой урок — «Кастомные виджеты» — соединит события шестого урока и примитивы этого в один навык: свой контрол с поведением и внешним видом. До этого стоит держать в голове каталог базовых виджетов, сетки пятого урока, которые сегодня выдавали примитивам размеры, и события шестого, которые заставят твой будущий виджет реагировать на клики. Картинки, кстати, грузятся и из ресурсов, вшитых в бинарник, — им посвящён шестнадцатый урок, а живое применение шкал и индикаторов ждёт в итоговом менеджере задач. Общий маршрут из двадцати пяти уроков — на странице курса Fyne. Домашнее задание соберёт из примитивов спидометр — и это будет твоя первая нарисованная, а не собранная из виджетов вещь.

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

Термин

canvas.Rectangle

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

Примитив-подложка: FillColor заливка, StrokeColor и StrokeWidth контур. MinSize нулевой — размер даёт контейнер или Resize; после смены поля нужен Refresh().

1 / 6

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

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

Виртуальный экран

HTML-модель виджет-дерева fyne
Canvas-примитивыfyne · мок

Примитивы рисования

Журнал событий

Экран собран: «Canvas-примитивы»

Коротко: что запомнить

  • Пакет canvas — материал всех виджетов: Rectangle, Circle, Line, Text, Image, Raster и LinearGradient собираются в любую нарисованную вещь.
  • Примитивы имеют нулевой MinSize: размеры диктует контейнер — GridWrap, Stack — или прямой Resize; в VBox без размера примитив невидим.
  • Смена поля примитива требует Refresh(): данные меняет код, экран обновляет только команда на перерисовку.
  • Line — диагональ своего ограничивающего прямоугольника: наклон вытекает из Position и Size, координатных полей нет.
  • canvas.Text — ручной текст с Color, Alignment и TextStyle; Label — виджет по теме; редактирование — территория Entry.
  • У Image три источника — File, Resource, Image — и FillMode: Stretch без пропорций, Contain с полями, Original в натуральную величину.
  • Цвета — color.NRGBA с альфой прозрачности или цвета темы; примитив запоминает значение и не перекрашивается при смене темы сам.

Домашнее задание

Нарисуй спидометр-заглушку: тёмный Rectangle-фон, поверх — дуга из десяти коротких Line, координаты концов посчитай через math.Sin и math.Cos, по центру — canvas.Text со значением. Кнопка «Плюс 10» увеличивает число, перекрашивает заполненные сегменты и зовёт Refresh у каждого изменившегося. Затем замени фон на canvas.LinearGradient с Angle и сравни, как выглядит ImageFillContain против ImageFillStretch на любой картинке — из файла или из embedded-ресурса. В конце перепиши ту же шкалу на widget.ProgressBar из третьего урока и честно сравни объём кода и гибкость: примитивы дают контроль, виджеты — скорость сборки. Добавь к спидометру клик-обработчик из шестого урока: тап по шкале ставит значение по позиции клика.

Личные заметкиПусто
Хранятся только в вашем браузере (Fyne · Canvas: примитивы рисования)

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

Почему примитив растянулся на всё окно?

Так делает компоновка: грид и Stack растягивают детей на свою площадь, а сопротивляться примитиву нечем — его MinSize нулевой. Ограничь размер GridWrap, положи примитив в Box или задай Resize; у примитива нет собственного мнения о размере.

Image не показывает картинку — что проверить?

Путь в File должен указывать на существующий файл, а FillMode не должен быть ImageFillOriginal при маленьком контейнере — в натуральную величину картинка уйдёт за края. Вшитые в бинарник картинки грузятся через NewImageFromResource с fyne.NewStaticResource; это надёжнее путей к файловой системе, особенно на мобильных.

Примитивы умеют скруглённые углы и тени?

У Rectangle есть CornerRadius для скругления, а сложные фигуры собираются из примитивов или рисуются в Raster вручную. Теней из коробки нет — их имитируют полупрозрачными слоями или рисуют градиентом; за этим уровнем контроля как раз и идут в canvas.

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

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

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

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

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

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

Cobra: CLI-приложения на Go

Интерактивный курс Cobra — фреймворка для CLI на Go: команды, флаги, конфиги Viper, автодополнение. 25 уроков с примерами кода, тестами и итоговым приложением.

  • Создавать CLI-приложения с деревом команд на Cobra
  • Работать с аргументами, флагами и их привязкой к конфигу
  • Управлять конфигурацией через Viper: файлы, env, приоритеты
  • Писать хуки PreRun/PostRun и корректно обрабатывать ошибки