Screens and screen language: экранные объекты
Преамбула: в этом руководстве хотелось осветить два больших пласта информации из официальных доков. Фактически, сделать его развернутым справочником по экранным объектам и их свойствам. Финальный объем переведенного текста с моими комментариями, пояснениями и примерами потянул на 50 страниц, и для статьи в блог это было избыточно. Поэтому было решено подробную справочную информацию (список свойств, параметры объектов) вынести в отдельный текстовый документ, а здесь оставить краткую часть, примеры, нюансы и разговоры за жисть.

Лирическое отступление: описанное в этом гайде актуально для версии 6.99.12. Я работаю на 6.99.9, и тестирование шло в основном на ней, но все конфликтные моменты были дополнительно проверены и отработаны на 6.99.12. Поведение в будущих версиях может измениться, гайд – устареть, да и вообще весь этот мир рано или поздно падет прахом.
апд: пока писался гайд, вышла 6.99.13... well, shit (на самом деле из свойств добавились только xspacing и yspacing, так что все норм).

Лирическое отступление в терминологию или Слишком много дисплейаблов!

В официальной документации подраздел, в котором описывается работа с экранными объектами, носит название "операторы пользовательского интерфейса" (user interface statements). Цитируя, "ОПИ создают дисплейабл-объекты и помещают их либо на экран, либо внутрь родительских дисплейабл-объектов". Все справедливо и понятно: frame, window, grid, button – все эти операторы создают соответствующие объекты, которые, в свою очередь, являются дисплейаблами.



Подытоживая, еще немного занудства:
- операторы screen language создают дисплейабл-объекты;
- дисплейабл-объекты (экранные объекты, дисплейаблы) могут быть одиночными или составными, а также служить контейнерами для других дисплейаблов.

Так что там насчет операторов пользовательского интерфейса?

Быстрое напоминание: то, каким будет любой созданный объект, задается с помощью параметров и свойств.

По характеристикам и назначению, свойства делятся на группы:
- общие: группа свойств, применимая к большинству дисплейабл-объектов, независимо от типа;
- позиционирующие: все те, которые отвечают за размеры и положение объекта относительно его контейнера. Применимы практически ко всем дисплейабл-объектам;
- стилевые свойства текстовых объектов: свойства, отвечающие за внешний вид текста (цвет, размеры, интервалы). Применимы только к текстовым объектам;
- стилевые свойства простых контейнеров: задают внешний вид простых контейнеров (фон, отступы). Применимы к простым контейнерам (окно, фрейм, кнопка);
- стилевые свойства кнопок: кнопка – это контейнер, но у нее есть специфические функции, которые описываются специфическими свойствами. Определяют поведение при нажатии и получении кнопкой фокуса. Применимы ко всем типам кнопок;
- стилевые свойства полос: отвечают за внешний вид полос прокрутки и регуляторов (громкости, etc.). Применимы только к одноименному типу экранных объектов;
- стилевые свойства контейнеров-коробок: отвечают за внешний вид специфических типов контейнеров, имеющих внутреннюю разметку (vbox, hbox), применимы только к ним;
- стилевые свойства фиксированной разметки: задают внешний вид объекта fixed. Применимы только к нему;
- стилевые свойства, применимые к трансформациям: особая группа свойств, которая применяется внутри ATL, и также к оператору add;

В приведенном документе перечислены свойства, входящие в каждую из категорий, с объяснением принципа работы, примерами и комментариями. Дополнительно там можно прочитать про:
- наследование стилей;
- стилевые префиксы/суффиксы;
- стилизацию различных состояний объектов;
- значения, которые могут принимать свойства.

Кроме текстовых объектов, изображений и контейнеров всех мастей, в список экранных объектов входят и такие специфические элементы управления, как полосы прокрутки и регуляторы, таймеры, и даже невидимый экранный объект, который обеспечивает привязку горячих клавиш к определенным функциям.

Пройдемся по списку.

Add

Добавляет внутрь экрана атомарный объект типа Displayable.

Зачем оно мне? Если хотим запихнуть в экран картинку как щупабельный объект, а не как часть фона. Картинка при этом может быть как статичным изображением, так и динамически сформированным средствами Ren’Py. Оператор add – твой бро, простой и понятный, с ним очень сложно ошибиться.
Подробнее здесь
Код
screen add_example:
  add "my-image.png" zoom 1.5 xoffset 100 yalign .5
  add Solid("#000")
  add Frame("my-image.png", 10, 10)

Bar (и vbar)

Добавляет горизонтальную (по умолчанию) полосу, способную выводить и изменять данные и подрабатывать полосой прокрутки. Vbar – абсолютно то же самое во всех отношениях, только ориентировано вертикально (по умолчанию). Пишу "по умолчанию" потому, что через стили их обоих можно заставить хоть на голове стоять.

Зачем оно мне? Чтобы создавать регуляторы громкости, полосы прокрутки, выводить уровни XP и HP, и много других прикольных штук.
Подробнее здесь

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

1. Выводить в виде шкалы некоторое значение, которое изменяется где-то еще.

Шкала здоровья, к примеру – очки здоровья отнимаются и прибавляются с помощью каких-то функций где-то в системе, тогда как полоса хп их просто красиво выводит на экране. В этом случае мы просто передаем в параметрах нужный range (максимальное значение хп), а value задаем в виде переменной, в которой у нас будет храниться текущее значение хп.

2. Управлять некоторым значением прямо с регулятора.

Например, громкость. Или скорость текста. Или прозрачность объекта. В этом случае нам понадобится либо тройка value-range-changed, либо один bar value внутри value, который выполняет функцию всех троих. Суть в том, чтобы задать не только текущее значение и максимальное, но и функцию, которая будет говорить регулятору, что ему делать, если ползунок таскают туда-сюда.

3. В роли старой доброй полосы прокрутки, если содержимое не вмещается в контейнер.

Тут нам будет нужен adjustment, а самое главное – правильный контейнер, который можно adjust’ить с помощью полосы прокрутки.

Примеры кода для всех трех случаев – по ссылке (там полный текст туториал-проекта, и много, чтобы тут место не занимать).

Лирическое отступление о круговых регуляторах: сейчас их приходится делать с помощью костылей. Один из способов (на основе картинок и кнопок увеличения/уменьшения) приложен в файле выше, второй – на основе стандартных регуляторов – описан в теме: https://lemmasoft.renai.us/forums/viewtopic.php?p=442211

Button

Добавляет на экран кнопку. Button без уточнения text- или image- в общем виде – это просто кусок области на экране, который будет выполнять заданные действия.

Зачем оно мне? Это ж кнопка! Всегда в хозяйстве пригодится!
Подробнее здесь
Код
screen button_test:
  button:
  text "Кнопка"
  action Jump("some label")

Fixed

Создает на экране контейнер для любого числа объектов.

Зачем оно мне? Явное добавление fixed-объекта на экран вам вряд ли понадобится. А вот неявное происходит у вас за спиной практически все время. Дело в том, что некоторые экранные объекты – формально – могут иметь строго один дочерний элемент. Если положить, например, во frame несколько элементов, то компилятор аккуратно сложит их в пакет-контейнер fixed, а сам fixed уже добавит во frame. Единственным дочерним элементом, как просили.

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


Frame

Добавляет на экран самую простую коробку-контейнер для элементов.

Зачем оно мне? Фрейм – твой самый большой бро среди контейнеров. Ему можно менять размеры, фон, позиционирование, он может быть как самостоятельным элементом, так и коробкой для других. Сложил кнопки внутрь vbox, получил меню – а стилизовать-то как? Положил vbox во фрейм – и жизнь наладилась!
Подробнее здесь
Код
screen test_frame():
  frame:
  xpadding 10
  ypadding 10
  xalign 0.5
  yalign 0.5

  vbox:
  text "Display"
  null height 10
  textbutton "Fullscreen" action Preference("display", "fullscreen")
  textbutton "Window" action Preference("display", "window")

Grid

Выводит дочерние элементы в виде сетки. Всем элементам выделяются ячейки одинакового размера – по размеру самого большого из них.

Зачем оно мне? Чтобы выводить таблички, или для экранов сохранений, или для ячеек инвентаря. Это все можно сделать и любыми другими контейнерами, но grid автоматизирует процесс за вас.
Подробнее здесь

Пример работы таблиц.
Код
screen grid_test:
  hbox:
  spacing 50

  grid 2 3:
  spacing 20

  text "1"
  text "2"
  text "3"
  text "4"
  text "5"
  text "6"
   
label start:
  show screen grid_test
  pause

Hbox и Vbox

Выводят дочерние элементы в ряд. Hbox – по горизонтали, vbox – по вертикали. Структурно эти братья-контейнеры ничем больше не отличаются.

Зачем оно мне? Чтобы выводить цепочку элементов друг за другом без возни с позиционированием: один элемент вывели, автоматически перешли на новую строку/в колонку, вывели следующий. Это правда удобно, учитывая, что frame (fixed) всех своих детей кладет в одну большую стопку в левом верхнем углу родителя. Используется чаще всего при создании меню

Коробки – твои первейшие бро при отладке игры, или когда гуи еще не нарисован.
Подробнее здесь
Код
screen hvbox:
  hbox:
  spacing 20

  vbox:
  spacing 20
  xsize 400

  text "Вы не поверите!"
  text "Мы запихнули текст в две вертикальные коробки-"

  vbox:
  spacing 20
  xsize 400

  text "-и обе вертикальные коробки-"
  text "Запихнули в горизонтальную коробку! Ого!"

Imagebutton

Документация скажет вам, что этот оператор добавляет на экран кнопку, состоящую из картинок, но я все еще считаю, что он добавляет картинки, которые зачем-то ведут себя как кнопки.

Зачем оно мне? Честно? Навскидку не скажу, чем imagebutton предпочтительнее button’а с настроенным под нужды фоном. Разве что код места меньше занимает, если кнопка простая. Возможно, это пережиток прошлого.
Подробнее здесь
Input

Добавляет на экран поле ввода текста. Когда юзер жмет Enter, текст в поле возвращается взаимодействию. Если экран с полем ввода вызвали с помощью оператора call, результат будет помещен в переменную _return.

Зачем оно мне? Ну как это зачем! Какая отомэ обходится без "введи свое имя, обыкновенный японский школьник"! Ладно, шутки в сторону – в паре своих новелл ТГ использовала поля ввода для запроса пароля к данным, которые игрок должен был получить по сюжету. Очень крутая фишка.
Подробнее здесь

Примеры кода – по ссылке (опять туториал-проект с комментариями, опять довольно много):
Key

Добавляет привязку экшена к горячей клавише. Действует все время, пока экран, в который он добавлен, находится на экране.

Зачем оно мне? Чтобы вызывать инвентарь кнопкой "I", конечно же! Или заставлять тян на экране глупо хихикать, если нажимаешь какую-нибудь кнопку, я не знаю. Или для мини-игр. Key нельзя пощупать или увидеть, но он твой бро, безусловно.
Подробнее здесь
Код
screen keymap_screen:
  key "O" action Show('some_screen') # вызывает экран some_screen по нажатию клавиши O

Label

Добавляет на экран контейнер, а в этот контейнер кладет текст. Label можно считать специфическим сплавом window/frame и text в один элемент, и в этом его главная фишка. И в связанных с этим тонкостях стилизации.

Зачем оно мне? Лэйбл – удобная штука. В основном за счет того, что с одной стороны это как бы текст, но одновременно как бы контейнер. Поэтому его можно стилизовать и как текстовый объект, и как объект-контейнер. Элементу text нельзя задавать фон, а лэйблу – можно. Комбинация frame и text, которой можно задавать фон – это два элемента, а лэйбл – один. Куда ни глянь, сплошная польза.
Подробнее здесь


Null

Добавляет на экран пустой элемент.

...Зачем оно мне? Ведь если я не хочу видеть элемент, я могу его не добавлять! Ну, не все так просто. Иногда возникает необходимость в элементе-заглушке. Помните, скажем, grid? Что, если у вас есть табличка 3х3, а элементов всего 7? Или вам нужно расставить их фигурно, через один? Мы не можем просто сказать grid’у "не клади сюда ничего". Но можем положить туда null.
Подробнее здесь


Код
screen null_grid:
  frame:
  grid 3 3:
  text "Раз"
  null
  text "Два"
  null
  text "Три"
  null
  text "Четыре"
  null
  text "Пять"

Пример чуть поинтереснее, с циклами – в текстовом файле по ссылке.
Mousearea

Добавляет на экран область, способную выполнять экшены, когда в нее попадает и из нее уходит курсор. В отличие от кнопок, mousearea не ворует фокус по ховеру, а просто триггерит определенные действия.

Зачем оно мне? Чисто технически – в решении многих задач mousearea может быть замещена кнопкой. Но она отлично подходит в случае, если нам нужно, скажем, подсвечивать кнопку или набор кнопок при наведении на область.
Код
screen button_overlay:
  mousearea:
  area (0, 0, 500, 100)
  hovered Show("buttons")
  unhovered Hide("buttons")

screen buttons:
  hbox:
  textbutton "Кнопка раз" action Jump("some_label")
  textbutton "Кнопка два" action Show ("some_window")

label start:
  show screen button_overlay

В данном примере мы хотим, чтобы меню показывалось при наведении на область. Для этого кладем поверх кнопок оверлей mousearea, и задаем ему соответствующие экшены.
Подробнее здесь
Side

Добавляет на экран специфическую сетку: делит доступную область на девять частей, присваивая им буквенный код: верх (‘t’), низ (‘b’), право (‘r’), лево (‘l’), левый верхний угол (‘tl’), левый нижний (‘bl’), правый верхний угол (‘tr’), правый нижний (‘br’) и центр (‘c’). После чего раскладывает дочерние элементы в эти ячейки. В отличие от grid, ячейки рендерятся не по размеру самой большой из них, а стремятся занять как можно меньше места.

Зачем оно мне? По ситуации. Работает side примерно в тех же случаях, что и grid, но часто используемым его не назовешь.
Подробнее здесь
Код
screen side_test:
  vbox:
  text "Это side:"
  side "c tl br":
  text "Центр"
  text "Левыйверхнийугол"
  text "Правый нижний угол"

  null height 50

  text "Это grid:"
  grid 3 3:
  text "Левыйверхнийугол"
  null
  null
  null
  text "Центр"
  null
  null
  null
  text "Правый нижний угол"

label start:
  show screen side_test
  "Сравните, как выглядят ячейки сетки {i}side{/i} и сетки {i}grid{/i} в зависимости от текста внутри."
  "{i}Side{/i} стремится занять как можно меньше места, {i}grid{/i} – уравнять элементы."

Text

Добавляет на экран текст. Зачем он нужен, и почему text – твой бро, думаю, пояснять не нужно.
Подробнее здесь
Код
screen text_test:
  text "А вы думали, добавление текстовых элементов скрывает какие-то тайны? Неа."

Textbutton

Добавляет на экран текстовую кнопку, объединяющую в себе свойства кнопок и лэйблов: внутрь кнопки неявным образом помещается текстовый объект.

Зачем оно мне? Textbutton – это как label, только еще и кнопка. Если вам нужен лэйбл-кнопка, вот вот, вы его нашли.
Подробнее здесь
Код
screen textbutton_test:
  vbox:
  textbutton "Кнопка раз" action Jump("some_label")
  textbutton "Кнопка два" action Show("some_screen")

Timer

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

Зачем оно мне? Масса применений. Для того, чтобы держать зрителя в напряжении, ограничивая время на ответ/действие в диалоге, на манер Теллтэйлов. Для подсчета времени, затраченного на что-либо. Для триггера определенных событий в нужное время. На основе таймера еще можно делать анимированные часы (хотя я предпочитаю использовать для этой же цели ATL).
Подробнее здесь
Код
screen timer_test:
  vbox:
  textbutton "Yes." action Jump("yes")
  textbutton "No." action Jump("no")

  timer 3.0 action Jump("too_slow")

Более сложный пример, имитацию теллтейловских диалогов, можно посмотреть в текстовом документе по ссылке.
Transform

Применяет заданные свойства трансформации к своему дочернему элементу. Сомневаюсь, что этот оператор может найти широкое практическое применение, когда есть свойство at и язык ATL.
Подробнее здесь
Код
screen transform_test:
  transform:
  rotate 45
  zoom 2

  text "I'm zoomed and rotated!"

Viewport

Добавляет на экран область-контейнер, который можно скроллить – колесом мыши, перетягиванием контента или с помощью полос прокрутки.

Зачем оно мне? Вьюпорт – ваш первейший бро, когда контента больше, чем места на экране. Логи диалогов персонажей? Вьюпорт. Окно смс-сообщений? Он же. Инвентарь? Аналогично.

Поскольку основная функция вьюпорта – хранить и управлять контентом, который не вмещается в экран, практически все его свойства реализуют разные возможности перемещения контента внутри вьюпорта (прокрутка мышью, перетягивание, создание скроллбаров и т.д.).
Подробнее здесь

Один из способов получить прокручиваемый viewport – присвоить ему id, а потом использовать его в комбинации с bar-объектами и XScrollValue() и YScrollValue().
Код
screen viewport_test:
  side "c b r":
  area (100, 100, 600, 400)

  viewport id "vp":
  draggable True

  frame:
  xysize(1000, 1000)

  bar value XScrollValue("vp")
  vbar value YScrollValue("vp")

Vpgrid

Введен в 6.99.10. Добавляет на экран объект, совмещающий в себе viewport и grid. Как обычно, имеет преимущества обоих типов объектов. Рендерит только те дочерние элементы сетки grid, которые в данный момент видны во viewport-области.

В отличие от grid, vpgrid предполагает, что все дочерние элементы одного размера, и приводит все ячейки к размеру первой. Также, в отличие от grid, vpgrid может принимать меньше дочерних элементов, чем в нем есть ячеек.
Подробнее здесь
Код
screen vpgrid_test:
  vpgrid:

  cols 2
  spacing 5
  draggable True
  mousewheel True

  scrollbars "vertical"

  for i in range(1, 100):

  textbutton "Button [i]":
  xysize (200, 50)
  action Return(i)

Window

Добавляет на экран окно-контейнер, предназначенный для вывода внутриигровых диалогов. В теории может использоваться как простой контейнер, только зачем, если есть frame?
Подробнее здесь
Код
screen say(who, what):
  window id "window"
  vbox:
  spacing 10

  text who id "who"
  text what id "what"

Операторы Imagemap

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

Лично я имэджмапы не люблю. Они грубые и негибкие, активные зоны на них могут быть только прямоугольными. Но для простых задач подходят.
Расширенные дисплейабл-объекты

Кроме простых операторов, язык экранов поддерживает операторы, которые соответствуют расширенным дисплейабл-объектам.



Обязательные параметры объекта в контексте языка экранов становятся параметрами оператора, необязательные параметры и стили становятся свойствами. Расширенные дисплейабл-объекты – это:

Drag

Cоздает дисплейабл-объект класса Drag(): объект, который можно перетаскивать внутри родительского контейнера. Помещать их можно в любой контейнер (зависит от ваших задач, в общем случае лучше всего подойдут fixed или draggroup).

Drag-объекты можно перетаскивать на другие drag-объекты, и тогда, если они находятся в одной группе, будет срабатывать событие дропа.

Перетаскивать drag-объекты можно только за пиксели с ненулевой прозрачностью. Если задано свойство focus_mask, то учитываются только прозрачность пикселей маски (пискели объекта при этом могут быть прозрачными).

Drag-объект может принадлежать только одной группе. Если добавить его в несколько, останется он только в последней. Если группа не указана явно, объект добавляется в группу по умолчанию.
Подробнее здесь

Draggroup

Cоздает дисплейабл-объект класса DragGroup(). Объединяет drag-объекты в группу. В качестве дочерних элементов принимает только их, в любом количестве. По физическим свойствам соотвествует объекту fixed (ведет себя как он, принимает те же свойства).

Взаимный дроп drag-объектов из разных групп не поддерживается, друг на друга дропаться могут только объекты одной группы. Также drag-объекты внутри группы нельзя вытаскивать за ее границы (границы контейнера).

В документации дан очень хороший пример использования, модификация в текстовом документе по ссылке. В общем и целом дрэг-объекты могут быть использованы для написания несложного интерактива, мини-игр (паззлы) и подобных штук.
Оператор Has

Некоторые дисплейаблы могут принимать строго один дочерний элемент. Если передать больше, то их неявным образом засунут внутрь контейнера fixed, помните? Так вот оператор has позволяет использовать вместо fixed любой другой контейнер из набора.

Условия:
- has можно употреблять только внутри контейнеров, которые могут иметь единственный дочерний элемент (button, frame, window).
- has принимает в качестве параметра название любого объекта, который может иметь несколько дочерних элементов (fixed, grid, hbox, side, vbox).

Касательно синтаксиса: оператор has отличается от явного объявления контейнера только тем, что не требует после себя двоеточия и отступа для детей – все перечисленные под ним элементы автоматически отправятся в указанный контейнер. В остальном не следует воспринимать его как случайную отдельную строчку, он неразрывен с нижеследующими элементами, и его нельзя указывать между свойств родительского контейнера. Все свойства, указанные после оператора has будут считаться свойствами контейнера, заданного в операторе has.



Что насчет grid и side, которым нужны обязательные параметры, в связке с has? Да, в общем-то, ничего, все как и раньше, указываем параметры сразу после имени контейнера:
Код
screen has_test:
  frame:
  has grid 2 1

  text "123"
  text "234"

Финальные слова, выводы и планы.

Получилось очень много, но вырезать что-то (кроме имэджмапов, которые и так везде есть), посчитав неважным, было бы нехорошо. На 60% это перевод официальных доков, на 40% – мои дополнения, пояснения и комментарии из собственного опыта. Хотелось не просто перечислить, что умеет Ren'Py, а примерно показать, зачем это может понадобиться на практике, и как это делать, если это не интуитивно ясно.

В далеком будущем хотелось бы осветить еще пару тем, в частности, создания curried-функций и CDD (UDD), но это глубоко позже. А на сегодня пока все. Спасибо за внимание.

(И еще раз, ссылки:
Справочник по экранным объектам
Справочник по стилевым свойствам)

Автор материала: Ikuku
Материал от пользователя сайта.



Ren'Py 07 Ноября 2017 791 Ikuku ren'py, screen language, экранные объекты, программирование, ТГ «Красавица Икуку» 5.0/3

Комментарии (2):
2
1 stop_control   (08 Ноября 2017 11:26)
53339
Большое спасибо ! )))

2
2 Sylenth   (08 Ноября 2017 20:48)
61899
Спасибо за такой обстоятельный разбор!

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]