Программирование на 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 продолжается до тех пор, пока открыт хотя бы один экранный виджет: после этого оно завершается, и выполнение приложение продолжается в обычном (консольном) режиме.
Строго говоря, оконное приложение — это обычное консольное приложение, которое вызывает 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], ),
);
Заметьте ещё, что здесь (в отличие от предыдущего примера) — мы передаём для каждого окна только координаты начальной точки. Размеры окна (т.е. его экстент), мы предлагаем задать самому виджету (что часто намного удобнее!) Впрочем, об этом, как и о многих других атрибутах и свойствах виджетов — ещё поговорим в дальнейших статьях.
Полный текст статьи читайте на overclockers.ru
