Темы



Программирование на Athanor – 20 (основы оконной графики)

Коротко сгенерировано ИИ по тексту статьи
  • Для графического вывода используется системная библиотека Winter, основанная на ООП и иерархиях классов.
  • Виджеты — объекты классов Widget и его подклассов; оконная система вызывает их методы открытия, перерисовки и закрытия.
  • В пространстве graphics описаны примитивы точек, линий и фигур, а афекторы задают цвета рисования и заполнения.

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

Основы графического интерфейса: окна, виджеты, графические примитивы и афекторы

Полагаю, что простой консольный ввод-вывод всем давно наскучил: куда интереснее попробовать разработать и что-то графическое! Основная сложность тут была в том, что для этого необходима системная библиотека «Winter» («WINdowing INterface»), где вся технология графического вывода — основана на механике ООП и на иерархиях классов. Поэтому, до двух последних статей об ООП — говорить об этом не было реальной возможности (теперь можно и поговорить).

ООП необходимо потому, что всё взаимодействие графического приложения с пользователем происходит через специальные интерактивные компоненты: виджеты (widget). Каждый виджет может не только рисовать что-то на экране –, но и получать ввод пользователя, через клавиатуру, мышь и другие устройства ввода. Технически, все виджеты — это объекты языка: экземпляры класса Widget (точнее говоря, его подклассов). При этом сам по себе Widget — типичный абстрактный класс, который предоставляет (в виде набора виртуальных методов) практически всё, что нужно, чтобы сделать виджеты реально работающими.

Все примеры, приведённые в этой статье (в готовой сборке с интерпретатором и библиотеками, для запуска достаточно распаковки архива) — можно скачать по этой ссылке. Эта ссылка будет постоянная, архив (надеюсь) будет понемногу пополняться по мере публикации новых статей.

Для того, чтобы начать использовать виджеты — прежде всего, надо добавить в начало программы следующую директиву:

library "Winter.ath";

Встроенный функтор library — работает примерно как аналог include (рассмотренный в статье 13), но более придирчиво. Он предназначен исключительно для «подключения» к программному модулю стандартных библиотек (в данном случае, библиотеки «Winter»). В отличие от include — он ищет подключаемые файлы и в системных библиотечных каталогах, а ошибки при подключении (или при инициализации графической системы) для library считаются фатальными (после них, весь остаток модуля просто не будет выполняться). Подключение любого библиотечного модуля через library — также неявно включает режим явного объявления глобальных переменных!

После успешной инициализации библиотеки Winter — станут доступны и виджеты. У суперкласса Widget есть немало виртуальных методов — пока что, мы рассмотрим следующие:

  • #on_open (Area): метод вызывается при открытии виджета
  • #on_paint (Area): метод вызывается при обновлении (перерисовывании) виджета
  • #on_close (Area): метод вызывается при открытии виджета

Разумеется, во всех этих случаях имя виртуального метода само не содержит символа »#» (но мы будем его ставить, как напоминание, что эти методы — виртуальные, и при переопределении их в производных классах про этот префикс нельзя забывать)! Все методы класса Widget — вызываются (как правило) не программой, а самой оконной системой. Метод on_open вызывается только один раз (в начале работы виджета), on_close — также только один раз (в конце работы виджета), но вот on_paint — может вызываться многократно. Реакцией на этот вызов — должна быть перерисовка виджета (или какой-то его части).

Как рисовать на виджете? Кроме суперкласса Widget — модуль Winter также экспортирует пространство имён graphics. Что такое «пространство имён»? Примерно то же, что определяет любой пользовательский класс: замкнутая область, где доступны собственные компоненты и функторы-методы. Пространства имён похожи на классы, хотя есть и немало различий. Например — у пространств имён не может быть мутабельных компонент, невозможна их инстанциация (т.е. нельзя создавать их экземпляры), и уж тем более невозможно создавать производные от них классы! Но есть и сходство: для доступа снаружи к пространству имён Name — нужен префикс (в виде Name!), и действует этот префикс на всё выражение, к которому он применён.

Пространство имён graphics — содержит множество функторов для работы с графикой. (На самом деле, в Winter определено не одно пространство имён –, но для нас пока в основном интересно именно это.)

Все функторы из graphics — делятся (в основном) на две большие подгруппы: графические примитивы и графические афекторы. Первые — рисуют в области виджета какие-то элементы (чаще всего, очень простые — откуда и название). Вторые — ничего не рисуют самостоятельно, но оказывают влияние на графические примитивы, выполняющиеся внутри их вызова: например, определяют для них цвета, стили, шрифты и пр. Мы начнём с основных графических примитивов:

graphics! plot (Point)
Рисует единственную точку (с координатами Point)
graphics! line (FromPoint, ToPoint)
Рисует отрезок, от точки FromPoint, до точки ToPoint

Здесь (как и далее) для всех функторов, находящихся в неком пространстве имён, мы будем ставить перед именем соответствующий префикс (в данном случае, graphics!). Каждый операнд, задающий точку — это просто список из двух целых чисел (X, Y), задающих соотвествующие координаты точки относительно рабочей области виджета (не экранные!) Список, задающий координаты — может быть как явным, так и результатом вычисления (например, быть взятым из переменной).

graphics!! plot (10, 20);

— рисует на виджете только одну точку, с координатами (10, 20)

graphics!! line ((100, 120), (120, 100));

— рисует на виджете прямой отрезок, от точки (100, 120) до точки (120, 100)

PointA = (50, 80);
PointB = (70, 120);
PointC = (30, 60);

graphics!! {
	line (PointA, PointB);
	line (PointB, PointC);
	line (PointC, PointA);
	};

— рисует на виджете треугольник (строго говоря, только три его стороны), вершины которого заданы переменными (PointA, PointB и PointC). Из последнего примера видно, что задавать точки в виде отдельных переменных — может быть удобно (если надо, например, передвинуть куда-то точку PointA — то её координаты нужно изменить в одном-единственном месте). Ещё из этого примера понятно, что префикс пространства имён — работает совершенно также, как и префикс класса: т.е. он действует на всё выражение, к которому был применён. В данном случае, выражением с префиксом является блок кода: во всём этом блоке, все функторы из пространства graphics становятся доступными. Это тоже весьма удобно, и этим мы постоянно будем пользоваться и дальше.

Система координат совершенно стандартная: левый верхний угол виджета является точкой (0, 0); координатная ось X тянется слева направо, а ось Y — сверху вниз. С координатами для этих графических объектов мы разобрались –, но у читателя должен возникнуть вопрос:, а как задать другие аттрибуты графического вывода? Например, точки и линии должны ещё иметь какой-то цвет: каким образом он задаётся? Действительно, в (большинстве) графических примитивов — цвет явно не определяется: он задан неявно, во внешней (для этого примитива) среде выполнения. Именно для этого и нужны графические афекторы! Например, афектор plot_color — задаёт цвет рисования: он действует на plot, line (и ещё довольно многие графические примитивы).

graphics! plot_color (PlotColor, @Body)
Определяет цвет рисования PlotColor для всех графических примитивов в Body

Все афекторы вычисляют свой операнд Body (как правило, второй) не сразу, а предварительно изменив среду его выполнения таким образом, что «цветом линий» для всех графических примитивов — будет PlotColor. Внутри выражения Body — могут выполняться любые вычисления (или действия): на всё другое (помимо графических примитивов) афектор никак не действует. Значение, возвращаемое афектором — это то значение, которое возвращает Body.

Что же касается самого цвета — это просто число, для задания которого удобно использовать набор функторов (также определённых в библиотеке Winter). Например:

  • RGB (valueR valueG valueB)

Задаёт цвет, в стандартной модели RGB (где valueR — красная компонента, valueG — зелёная, и valueB — синяя). Каждая компонента — целое число, которое может менять значения от 0 (минимум) до 255 (максимум). Цвет RGB (0,0,0) — абсолютно чёрный, RGB (255,255,255) — абсолютно белый. Вообще, для восьми самых «чистых» RGB-цветов — определены удобные конструкторы:

  • Red (n): чистый красный цвет
    (эквивалентно RGB (n, 0, 0))
  • Green (n): чистый зелёный цвет
    (эквивалентно RGB (0, n, 0))
  • Blue (n): чистый синий цвет
    (эквивалентно RGB (0, 0, n))
  • Yellow (n): чистый жёлтый цвет
    (эквивалентно RGB (n, n, 0))
  • Cyan (n): чистый голубой (или «циановый») цвет
    (эквивалентно RGB (0, n, n))
  • Magenta (n): чистый фиолетовый (или «пурпурный») цвет
    (эквивалентно RGB (n, 0, n))
  • White (n): чистый белый/серый цвет
    (эквивалентно RGB (n, n, n))
  • Black (n): чистый чёрный цвет
    (эквивалентно RGB (0, 0, 0) для любого n)

Во всех этих конструкторах — аргумент n задаёт интенсивность цвета: 0 — минимальная, 255 — максимальная. Любой цвет с интенсивностью 0 — это просто чёрный; Black (n) — тоже просто чёрный, независимо от n. Параметр n может быть просто опущен: тогда вместо него используется 255, т.е. самый интенсивный цвет.

Теперь, надеюсь, понятно, как выводить графические примитивы в цвете:

graphics!! plot_color (Green (128)):: line ((100, 120), (120, 100));

— рисует на виджете отрезок, от точки (100, 120) до точки (120, 100), но уже с явно заданным цветом (средне-зелёным).

graphics!! plot_color (Magenta (64))::
	for_inc (x, 8,
		for_inc (y, 8,
			plot (64 + x*16, 128 + y*32)
			));

— рисует на виджете 64 точки (в узлах прямоугольной сетки 8×8), с явно заданным цветом (тёмно-фиолетовым). В этом примере — мы, разумеется, могли бы применить афектор plot_color к каждому вызову примитива plot. Но тогда он тоже был бы вызван 64 раза, а это очень неэффективно — намного лучше вызвать его один раз, применительно к обеим вложенным циклам, которые выводят все точки.

Графические примитивы, конечно, не ограничиваются линиями! Вот основные примитивы, рисующие и более сложные объекты:

graphics! rect (LeftTop, RightBottom)
Рисует заполненный прямоугольник, в области [LeftTop, RightBottom]
graphics! oval (LeftTop, RightBottom)
Рисует заполненный эллипс (овал), в области [LeftTop, RightBottom]
graphics! round_rect (LeftTop, RightBottom, Corners)
Рисует заполненный прямоугольник с округлёнными углами (с радиусами Corners), в области [LeftTop, RightBottom]

Во всех этих примитивах — используется ещё одна важная концепция: прямоугольная область. Она обычно задаётся парой точек: верхней/левой (LeftTop) и нижней/правой (RightBottom), которые определяют её однозначно. Примитив rect рисует в заданной области прямоугольник, примитив oval — рисует вписанный в область овал (т.е. эллипс). Последний примитив round_rect — рисует нечто среднее между ними: прямоугольник, но с эллиптически-закруглёнными углами. Для него также требуется третий параметр Corners, задающий радиусы закругления, в виде (RadiusX, RadiusY) — они по обеим осям могут и различаться. (Если один из этих радиусов равен нулю — закругённые углы отстутствуют, т.е. результат тот же, что и для rect.)

Для этих трёх графических примитивов — рисуемые ими объекты имеют не только периметр, но и внутреннюю область, которая тоже заполняется цветом. А каким именно — определяется отдельным афектором:

graphics! fill_color (FillColor, @Body)
Определяет цвет заполнения FillColor для всех графических примитивов в Body

Цвет заполнения (FillColor) — задаётся совершенно так же, как цвет рисования в plot_color, но действует он уже только на графические примитивы, имеющие внутреннюю закрашиваемую область (а на plot и line — вообще не действует). Хотя оба цвета (рисования и заполнения) — конечно, могут совпадать, но в общем случае они различаются. Например:

graphics!! {
	fill_color (Magenta ()):: plot_color (Green ()):: rect ([100 100], [150 200]);
	fill_color (Cyan ()):: plot_color (Yellow ()):: oval ([100 100], [150 200]);
	};

Здесь мы сперва рисуем прямоугольник (от верхней/левой точки (100, 100), до нижней/правой точки (150, 200)), окрашенный в фиолетовый цвет с зелёной окантовкой. Потом добавляем (аккурат поверх прямоугольника) ещё и овал, окрашенный в голубой цвет с жёлтой окантовкой. Результат будет потрясающе красивым (или ужасающе вырвиглазным: представления о прекрасном у всех свои).

Вызовы всех графических афекторов могут быть вложенными: внутренние афекторы просто (временно) переопределяют внешние. В следующем примере:

graphics!! plot_color (Blue ()):: {
	line ([100 100], [150 100]);
	plot_color (Red ())::
		line ([100 200], [150 200]);
	line ([100 300], [150 300]);
	};

— мы выводим (точно друг под другом) три горизонтальных отрезка: верхний и нижний будут нарисованы синим цветом, а находящийся посередине между ними — красным. Все афекторы имеют временный эффект: они действуют на свой операнд — но, по его выполнении, восстанавливают изменённый аттрибут графики (такой, как цвет) к его предыдущему значению. В этом отношении, афекторы работают совсем как префикс доступа к объекту (операция ».»): только они меняют не текущий экземпляр класса –, а текущий цвет (или какой-нибудь ещё атрибут графики). На самом деле, и цвета, и все прочие графические атрибуты — тоже принадлежат некому (внутреннему для Winter) объекту, который называется графическим контекстом. Вне графического контекста — графические функторы (и примитивы, и афекторы) работать просто не будут! Как мы ещё увидим, во многих случаях графический контекст надо определять явно –, но при вызове метода ! on_paint это не нужно, т.к. здесь он создаётся автоматически.

За пределами всех графических афекторов, применяются атрибуты по умолчанию: цветом рисования (plot_color) является абсолютно-чёрный (RGB (0,0,0)), а цветом заполнения (fill_color) — абсолютно-белый (RGB (255,255,255)). Но при обычном выводе графических объектов — какой-то цвет всегда определён! Если же нужно сделать соответствующий цвет невидимым (или «прозрачным») — то для этого нужны отдельные афекторы:

graphics! plot_disable (PlotFlag, @Body)
Отключает (если PlotFlag ненулевой) рисование (для всех графических примитивов в теле Body)
graphics! fill_disable (FillFlag, @Body)
Отключает (если FillFlag ненулевой) заполнение (для всех графических примитивов в теле Body)

При истинном (ненулевом) параметре PlotFlag, афектор plot_disable просто отключает рисование линий (для всех графических примитивов в теле Body, которые их рисуют). Так же, при истинном (ненулевом) параметре FillFlag — афектор fill_disable отключает заполнение внутренних областей (для всех графических примитивов в теле Body, у которых такие области есть). Оба афектора действуют независимо друг от друга. Но их можно применить и одновременно: если выполнить plot_disable (1):: fill_disable (1):: { Code } — тогда внутри блока Code вообще ничего не будет нарисовано (по крайней мере — из уже известных нам графических примитивов). В принципе, и это иногда может быть полезно.

Есть графический примитив специально для заполнения:

graphics! fill (LeftTop, RightBottom)
Заполняет прямоугольную область [LeftTop, RightBottom]

Параметры здесь те же, что и для rect: фактически, тоже рисуется прямоугольник, но уже без периметра. Поэтому, на этот примитив действуют только афекторы fill_color (и fill_disable). Примитив fill в основном полезен для быстрого заполнения прямоугольных областей.

Наконец, можно рисовать произвольные многоугольники:

graphics! polygon
(Point0, Point1, … PointN,)
Рисует (заполненный) многоугольник (полигон), с вершинами Point0 … PointN

Многоугольник — определяется списком своих вершин (от Point0 до PointN). Обратите внимание: для polygon список аргументов-точек должен быть открытым! Если у многоугольника N+1 вершина — то ровно столько же и сторон (от Point0→Point1 до PointN→Point0). Многоугольник всегда является замкнутой фигурой, поэтому к нему применяются как афекторы рисования сторон (plot_color/plot_disable), так и афекторы заполнения областей (fill_color/fill_disable). Многоугольник не обязан быть выпуклым (более того, он может даже иметь самопересечения).

Все рассмотренные в этой статье графические примитивы и афекторы — мы проверим на примерах. Первый пример: несколько тривиальных виджетов, которые просто рисуют на своей рабочей области точки, вертикальные и горизонтальные линии. Цвета фона и рисования, расстояния по вертикали/горизонтали и пр. — задаются как параметры виджетов. Однако, у виджета есть обязательный первый параметр: параметр суперкласса, который определяет строку заголовка для окна!

` Вертикальная линия: с координатой 'x', через весь виджет по высоте `
! V_Line(x) = graphics!! line ([x 0], (x, Widget!!Height));

` Горизонтальная линия: с координатой 'y', через весь виджет по ширине `
! H_Line(y) = graphics!! line ([0 y], (Widget!!Width, y));

` Заполняет виджет фоновым цветом 'BgColor' `
!! [Widget] BlankFill (BgColor) {

	! #on_paint (L_T R_B) =
		` (clear widget area) `
		graphics!! fill_color (BgColor):: fill (L_T, R_B)

	};		` -- BlankFill `

` Заполняет виджет горизонтальными линиями
  (с цветом 'FgColor', вертикальным шагом 'VStep' и фоновым цветом 'BgColor') `
!! [Widget] HLinesFill (BgColor FgColor VStep) {

	! #on_paint (L_T R_B) : [Y] = graphics!! {
		` (clear widget area) `
		fill_color (BgColor):: fill (L_T, R_B);

		` (draw horizontal lines with step 'VStep') `
		plot_color (FgColor)::
			for_inc (Y, Height % VStep + 1, H_Line(Y * VStep))
		}		` -- on_paint `

	};		` -- HLinesFill `

` Заполняет виджет вертикальными линиями
  (с цветом 'FgColor', горизонтальным шагом 'HStep' и фоновым цветом 'BgColor') `
!! [Widget] VLinesFill (BgColor FgColor HStep) {

	! #on_paint (L_T R_B) : [X] = graphics!! {
		` (clear widget area) `
		fill_color (BgColor):: fill (L_T, R_B);

		` (draw vertical lines with step 'HStep') `
		plot_color (FgColor)::
			for_inc (X, Width % HStep + 1, V_Line(X * HStep))
		}		` -- on_paint `

	};		` -- VLinesFill `

` Заполняет виджет вертикальными/горизонтальными линиями
  (с цветом 'FgColor', горизонтальным шагом 'VStep', вертикальным шагом 'VStep' и фоновым цветом 'BgColor') `
!! [Widget] RLinesFill (BgColor FgColor HStep VStep) {

	! #on_paint (L_T R_B) : [X Y] = graphics!! {
		` (clear widget area) `
		fill_color (BgColor):: fill (L_T, R_B);

		plot_color (FgColor):: {
			` (draw vertical lines with step 'HStep') `
			for_inc (X, Width % HStep + 1, V_Line(X * HStep));
			` (draw horizontal lines with step 'VStep') `
			for_inc (Y, Height % VStep + 1, H_Line(Y * VStep))
			}
		}

	};		` -- RLinesFill `

` Заполняет виджет прямоугольной сеткой из точек
  (с цветом 'FgColor', горизонтальным шагом 'VStep', вертикальным шагом 'VStep' и фоновым цветом 'BgColor') `
!! [Widget] RPointsFill (BgColor FgColor HStep VStep) {

	! #on_paint (L_T R_B) : [X Y] = graphics!! {
		` (clear widget area) `
		fill_color (BgColor):: fill (L_T, R_B);

		` (draw point grid with step 'HStep'/'Vstep') `
		plot_color (FgColor)::
			for_inc (X, Width % HStep + 1,
				for_inc (Y, Height % VStep + 1,
					plot(X * HStep, Y * VStep)
					)
				);
		}

	};		` -- RPointsFill `

Все виджеты здесь описаны корректно, но если запустить этот код — он отработает без ошибок, и сразу завершится. Очевидно, что чего-то не хватает. Недостаточно просто декларировать виджет — необходимо ещё и запустить его в работу! А работать виджет может только в рамках т.н.»оконной сессии» — поэтому, надо её запустить:

WinSession (
	(RPointsFill ("Points grid (Black/White, HS=15, VS=10)",
		White(), Black(), 15, 10),
			[100 100], [600 400]),

	(BlankFill ("Blank [Red]", Red()), [100 100], [200 200]),
	(BlankFill ("Blank [Green]", Green()), [200 200], [200 200]),
	(BlankFill ("Blank [Blue]", Blue()), [300 300], [200 200]),

	(HLinesFill ("H-Lines [Blue/Yellow, VS=15]",
		Yellow(), Blue(), 15),
			[300 100], [200 200]),
	(VLinesFill ("V-Lines [Green/Magenta, HS=25]",
		Magenta(), Green(), 25),
			[400 200], [200 200]),
	(RLinesFill ("H/V-Lines [Red/Cyan, HS=20/VS=30]",
		Cyan(), Red(), 20, 30),
			[500 300], [200 200]),
	);

В рамках одной оконной сессии — может быть открыто не одно, а несколько окон (в данном случае — мы открываем целых семь)! Каждый параметр в открытом (!) списке операндов для WinSession — тоже список, описывающий открываемое окно. Первый операнд — собственно, виджет (он отображается в окне); второй — координаты начальной (левой и верхней) точки окна, третий — экстент (ширина и высота) окна. Выполнение WinSession продолжается до тех пор, пока открыт хотя бы один экранный виджет: после этого оно завершается, и выполнение приложение продолжается в обычном (консольном) режиме.

C-20_DrawLines

Строго говоря, оконное приложение — это обычное консольное приложение, которое вызывает WinSession, временно переключаясь таким образом в режим оконного интерфейса. Даже при работе оконной сессии — приложение может выполнять вывод на консоль (но не ввод с неё). На самом деле, приложение может даже запускать несколько последовательных оконных сессий (правда, такое редко бывает нужным). Однако, оконные сессии нереентрантны: чтобы начать новую оконную сессию, сперва надо завершить предыдущую! (Не следует путать оконные сессии и сами окна: открывать новые окна — уже в процессе работы сессии, и до её завершения — вполне допустимо, но об этом подробнее потом.)

Второй пример показывает вывод различных графических фигур (как и действие на них вложенных графических афекторов).

` Прямоугольная сетка из графических объектов
  (вызов "object_draw ! region" используется для рисования объекта в заданной прямоугольной области) `
!! [Widget] ColorObjectsGrid (object_draw S CT LT RB)

	= (Width = Height = 8 * S)

	{

	! #on_paint (L_T R_B) : [R C] = graphics!! {
		` (draw horizontal line at 'y') `
		! H_Line (y) = line ((L_T[0], y), (R_B[0], y));

		` (draw vertical line at 'x') `
		! V_Line (x) = line ((x, L_T[1]), (x, R_B[1]));

		` (paint widget area) `
		fill_color (Grey(Normal)):: fill (L_T, R_B);

		` (horizontal gridlines) `
		for_inc (C, 8,
			plot_color (RGB8(C)):: H_Line (C*S + CT)
			);

		for_inc (R, 8,
			plot_color (RGB8(R)):: {
				` (vertical gridlines) `
				V_Line ((R =*: S) + CT);

				` (objects grid) `
				for_inc (C, 8,
					fill_color (RGB8(C)):: {
						C =*: S;
						object_draw ! ((C+LT, R+LT), (C+RB, R+RB))
						})	` -- for_inc (C) `
				})	` -- for_inc (R) `
		}		` -- on_paint `

	};		` -- ColorObjectsGrid `

Виджет ColorObjectsGrid рисует в узлах прямоугольной сетки (8×8) различные фигуры. Цвет которых меняется: по горизонтали — меняется цвет заполнения, по вертикали — цвет рисования линий. Для задания цвета используем ещё один способ: вызов RGB8 (N) — задаёт один из восьми основных цветов: чёрный, синий, зелёный, голубой, красный, фиолетовый, жёлтый, белый. На самом деле, у RGB8 есть и второй аргумент (интенсивность цвета), но мы его здесь просто опускаем. Какая именно фигура рисуется — задаётся аргументом виджета object_draw. Мы в трёх разных окнах нарисуем три типа объектов: квадраты, круги и ромбы. Первые два тривиальны: для рисования просто вызываем graphics! rect и graphics! oval. А встроенного примитива для рисования ромбов в библиотеке Winter не существует — но, с помощью graphics! polygon, его нетрудно определить:

! Rhomb (LeftTop RightBottom) : [L R C_X T B C_Y] = {
	[L T] = LeftTop;
	[R B] = RightBottom;
	[C_X C_Y] = ((L + R) % 2, (T + B) % 2);

	graphics!!polygon (
		[C_X T],
		[L C_Y],
		[C_X B],
		[R C_Y],
		);
	};	` -- Rhomb `

Осталось запустить оконную сессию с тремя окнами (в каждом — виджет ColorObjectsGrid, но с разными параметрами). Параметры виджета (шаг сетки, сдвиги относительно сетки и пр.) — одинаковы для всех трёх окон (поэтому мы их собрали в отдельный список-константу param_list):

:( param_list = [45 20 5 35] );

WinSession (
	(ColorObjectsGrid ("Color circles grid",
		!(Area) = (graphics!!oval (Area)), param_list),
		[100 50], ),

	(ColorObjectsGrid ("Color squares grid",
		!(Area) = (graphics!!rect (Area)), param_list),
		[160 75], ),

	(ColorObjectsGrid ("Color rhombs grid",
		!(Area) = (Rhomb (Area)), param_list),
		[220 100], ),
	);

C-20_DrawShapes

Заметьте ещё, что здесь (в отличие от предыдущего примера) — мы передаём для каждого окна только координаты начальной точки. Размеры окна (т.е. его экстент), мы предлагаем задать самому виджету (что часто намного удобнее!) Впрочем, об этом, как и о многих других атрибутах и свойствах виджетов — ещё поговорим в дальнейших статьях.

Полный текст статьи читайте на overclockers.ru