Перейти к основному содержимому

Morphic — графическая система

Разработчику

Morphic

См. также: Справочник §12 · Pharo · Squeak · ООП в Smalltalk · Крестики-нолики · SmallDesktop · Raylib в Pharo · Десктопные приложения


Что такое Morphic?

Morphic — графический каркас Smalltalk, в котором каждый элемент интерфейса — объект. Базовый класс — Morph (от "metamorphosis", метаморфоза): прямоугольная область с координатами, размером, цветом, дочерними морфами и методами отрисовки.

Ключевые термины:

  • Морф (Morph) — визуальный объект; умеет drawOn:, принимать события, содержать детей.
  • World — корневой морф рабочего стола Pharo / Squeak.
  • Submorphs — дочерние морфы; дерево submorphs — это сцена UI.
  • Halos — "ореолы" для изменения размера и меню морфа (в режиме разработки).

Morphic используют Pharo и Squeak. В старых системах Smalltalk-80 был слой Views (классический MVC); Morphic его заменил единым деревом объектов. Исторически Morphic создали в Apple, затем перенесли в Squeak; Pharo унаследовал и развивает его.

Главная идея: UI не описывают XML или HTML, а собирают из живых объектов, которые можно инспектировать в Debugger, сохранить в image и изменить на лету — в духе live coding.

Связь с разделом "Десктоп"

Окна, фокус, события клавиатуры и цикл "событие → обработчик → перерисовка" в Morphic — частный случай тем из Десктопных приложений. Там — общая теория; здесь — конкретная реализация в Smalltalk.


Морф и дерево сцены

Каждый морф в типичном случае:

  • хранит bounds — прямоугольник в координатах родителя;
  • рисует себя в drawOn: (или наследует заливку через color);
  • содержит submorphs — список дочерних морфов;
  • получает события мыши и клавиатуры, если ответил true в handlesMouseDown: или держит фокус клавиатуры.

Пример — панель с подписью

panel := Morph new.
panel color: Color lightGray.
panel extent: 300 @ 200.

label := StringMorph contents: 'Привет, Morphic!'.
label position: 20 @ 20.
panel addMorph: label.

panel openInWorld

Разбор построчно:

СтрокаСмысл
Morph newСоздание экземпляра; new — сообщение классу Morph
panel color: Color lightGrayУстановка цвета заливки; Color lightGray — объект-цвет
panel extent: 300 @ 200Размер 300×200; @ создаёт объект Point
StringMorph contents: '…'Морф для однострочного текста
label position: 20 @ 20Смещение относительно родителя (пока label ещё без родителя — от (0,0) world при open)
panel addMorph: labelВложить label в panel; координаты position теперь относительно panel
panel openInWorldПоказать на рабочем столе Pharo

Полезные сообщения:

  • openInHand — морф "приклеен" к курсору для перетаскивания;
  • delete — убрать с экрана;
  • extent: / position: — геометрия вручную (как в практикумах раздела).

Основные классы морфов

КлассНазначение
MorphБазовый контейнер и "холст"
BorderedMorphПрямоугольник с рамкой
StringMorphОднострочный текст
TextMorphМногострочный редактируемый текст
SimpleButtonMorphКнопка через target + actionSelector
PluggableTextMorphПоле ввода, связанное с моделью (TextModel)
ImageMorphРастровое изображение
ScrollPaneПрокручиваемая область
SystemWindowОкно с заголовком и кнопками

Где это уже использовано в энциклопедии:

Полный список паттернов — §12 справочника.


События мыши и клавиатуры

Morphic не опрашивает клавиатуру в цикле while. Система доставляет события морфу под курсором или морфу с фокусом. Переопределяемые методы:

МетодКогда вызывается
handlesMouseDown:Разрешить ли клик (вернуть true)
mouseDown:Кнопка мыши нажата
mouseUp:Отпущена
mouseMove:Движение с зажатой кнопкой
handlesKeyboard:Принимать ли клавиши
keyStroke:Символ с клавиатуры (нужен фокус)

Паттерн pluggable-кнопки

btn := SimpleButtonMorph new.
btn label: 'OK'.
btn target: self.
btn actionSelector: #buttonPressed.

Разбор:

  • SimpleButtonMorph new — готовый морф-кнопка без подкласса.
  • label: — текст на кнопке.
  • target: — объект, которому пошлют действие (часто self доски или формы).
  • actionSelector: — символ имени унарного метода, например #buttonPressed.

По клику кнопка шлёт buttonPressed объекту target. Это тот же приём, что в крестиках-ноликах для "Новая игра".

Фокус клавиатуры в играх: перед WASD или стрелками кликните по окну игры. Иначе keyStroke: уйдёт другому морфу — типичная ошибка в SmallPong и SmallShooter.


Stepping — игровой цикл Morphic

В Morphic нет отдельного main() с while running. Анимация и игры использую stepping:

  1. Морф вызывает self startStepping.
  2. Система периодически шлёт сообщение step с интервалом stepTime (миллисекунды).
  3. Остановка — stopStepping.
stepTime
^ 16 "≈ 60 кадров в секунду"

step
self movePlayer.
self checkCollisions.
self changed

Разбор:

ЭлементРоль
stepTimeПауза между вызовами step; 16 ms ≈ 62 FPS
stepОдин "тик" игры: логика + запрос перерисовки
changedПросит Morphic перерисовать bounds морфа

Сравнение с Python Pygame: там вы сами крутите while running и clock.tick(60). В Morphic система вызывает step — модель остаётся чистой.

Разделение слоёв (как в SmallPong):

  • PongGame — координаты, счёт, правила; не знает о Morphic;
  • PongGameMorph>>stepgame tick, затем двигает дочерние морфы ракеток и мяча.

Отрисовка — drawOn и changed

По умолчанию Morph>>drawOn: заливает прямоугольник цветом color. Своя графика — переопределение:

drawOn: aCanvas
super drawOn: aCanvas.
aCanvas
lineColor: Color white;
drawLineFrom: 0 @ (self height // 2) to: self width @ (self height // 2)

Разбор:

  • super drawOn: aCanvas — сначала фон (заливка color).
  • aCanvas — объект-кисть; сообщения lineColor:, drawLineFrom:to: рисуют линию.
  • self height // 2 — целочисленное деление; // — см. типы и числа.

После изменения состояния обязательно self changed. Без него экран может не обновиться до следующего полного цикла — частая ошибка новичков.

Пример кастомного поля — PongPlayfieldMorph (пунктирная линия посередине).


Компоновка и окна

Ручная компоновка (все практикумы раздела):

  • addMorph: — добавить ребёнка;
  • removeAllMorphs — очистить контейнер (список заметок в SmallDesktop);
  • position: / extent: — абсолютное размещение.

Автоматическая компоновка (для больших форм):

  • layoutPolicy:TableLayout, ProportionalLayout и др.;
  • hResizing / vResizing — растягивание при изменении окна.

SystemWindow — обёртка с заголовком. Открытие из практикума:

TTTBoardMorph class >> open
^ self new openInWindow

openInWindow создаёт SystemWindow, помещает морф внутрь и показывает на World — пользователь видит "обычное" окно с заголовком.


Morphic и MVC

MVC (Model–View–Controller) впервые цельно описали в Smalltalk-80. В практикумах энциклопедии — упрощённый MVC:

СлойПример классаОбязанность
ModelTTTGame, PongGameПравила и состояние; без импорта Morphic
ViewTTTCellMorph, морфы ракетокОтображение
ControllerTTTBoardMorphКлики/клавиши → вызовы модели → refresh

Модель можно тестировать в Playground:

g := TTTGame new.
g playAt: 1.
g playAt: 5.
g winner "→ проверка логики без UI"

Морфы знают о модели, но не дублируют правила победы — см. ООП-модель и крестики-нолики.


Morphic и внешние библиотеки

MorphicRaylib через FFI
ЗависимостиТолько imageraylib.dll / .so
ОкноВнутри World PharoОтдельное OS-окно
Игровой циклstep / stepTimewhile !WindowShouldClose()
ОтладчикМежду кадрами в PharoСложнее при падении FFI
ПрактикумыTTT, SmallDesktop, SmallPong, SmallShooterДополнение для нативного рендера

Для учебных GUI и 2D-аркад в разделе Smalltalk достаточно Morphic. Raylib — следующий уровень, когда нужны текстуры, шейдеры и звук как в играх на C++.


Частые ошибки

СимптомПричинаРешение
MessageNotUnderstood при кликеНе вызван initialize / не создан labelMorphПроверить порядок инициализации
Клавиши "молчат"Нет фокусаКлик по окну игры
"Замороженная" картинкаНет changedВызвать после изменения модели
Индексы съехалиПутаница 0-based и 1-basedArray>>at: в Pharo с 1
ЛагиТяжёлая логика в stepВынести в модель; упростить drawOn:

Больше типичных сбоев — FAQ итогов.


Маршрут обучения

ШагМатериалНавык
1§12 справочникаШпаргалка API
2Крестики-ноликиКлики, кнопка, MVC
3SmallDesktopФормы, панели, тема
4SmallPong или SmallShooterStepping, клавиатура

Историческая справка

Morphic разработали Дэн Ингаллс и команда в Apple (1990-е), затем перенесли в Squeak, заменив устаревший MVC Views. Единое дерево морфов упростило direct manipulation — перетаскивание, halos, скрипты Etoys. Pharo унаследовал Morphic и добавляет Spec2 (декларативные формы) для "взрослых" приложений; учебный путь раздела сознательно идёт через классические морфы, чтобы вы видели объекты на экране буквально.