Журнал LinuxFormat - перейти на главную

LXF132:Python

Материал из Linuxformat
Перейти к: навигация, поиск
Учебник Python Настоящие задачи, чтобы вы поупражнялись в кодировании

Содержание

Python: Анимация интерфейса

Clutter всецело ориентирован на разработку высококлассных пользовательских интерфейсов. Ник Вейч приправит графику, слегка вышколив кнопки.

Clutter был создан для «оживления» графических интерфейсов, и нам пора посмотреть, как из объектов-актеров создаются кнопки, а затем добавить к ним несколько занятных атрибутов. Но для начала, прежде чем демонстрировать поразительные новые навыки общения с мышью, надо припасти что-то, с чем мы будем взаимодействовать. Как и почти все инструментарии, Clutter является событийно-ориентированным. То есть, если в приложении происходит что-то значимое, генерируется сигнал. Грубо говоря, другие участки кода ждут этого сигнала и делают что-то в ответ.

Если в Clutter отметить актера как реагирующего [reactive], то при любом событии с ним – типа щелчка, переноса, движения над ним курсора мыши и даже набора на клавиатуре – он будет генерировать полный набор сигналов. Каждый из них можно подключить на уровне объекта к методу или функции обратного вызова, обеспечивающей реакцию на сигнал. Чтобы опробовать это, не потребуется даже создавать приложение: все делается в интерактивной оболочке Python. Откройте терминал и введите python, а затем начните ввод (если вы ленивы, можете скопировать код из файла-листинга, имеющегося на LXFDVD). Приступим:


 import clutter
 
 def entered(actor,event):
  print event,actor
  actor.set_color(clutter.Color(0,0,255,255))
  return#t’
 
 def exited(actor,event):
  print event,actor
  actor.set_color(clutter.Color(255,0,255,255))
  return#t’

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

По сути, entered() и exited() – просто обычные функции. Обе получают два параметра, объекты actor [актер] и event [событие]. Вначале они выводят эту информацию (что удобно при отладке, для просмотра, какой объект что сгенерировал, но в итоговом приложении не нужно), затем используют унаследованный актером метод set_color, для изменения его цвета. Здесь мы просто определили объекты clutter.Color на лету. Они принимают значения RGBA, так вот мы установили синий в первом и фиолетовый во втором случае.

Отцы и дети

Последняя строка может вызвать удивление: зачем мы возвращаем значение из события? И почему такое странное? Происходит следующее: Clutter получает сигнал event на родительском объекте, сцене, которая всегда является реагирующей. В предыдущих уроках нашей серии мы просто соединяли сигналы напрямую от сцены – прошлый раз, например, в обработчике нажатий клавиш. Но родительский объект также просматривает всех своих потомков, пытаясь определить, кто из них ответственен за эффект. Это может быть, например, одинокий прямоугольник в углу или часть в большей группе объектов, являющейся потомком сцены. Когда «виновный» потомок найден, он генерирует сигнал, «всплывающий» вверх к родителю. Это необходимо потому, что если прямоугольник не имеет явного обработчика, его может иметь родительский объект, и щелчок на прямоугольнике будетобрабатываться на уровне группы.

Этот процесс – перехват событий в так называемой «пузырьковой» фазе – действие стандартное, но оно тратит ресурсы, продолжая передавать событие по всем объектам, даже если оно уже обработано. Изящно пресечь это можно, прервав процесс, по типу выхода из цикла – именно это и происходит, когда функция обработки сигнала возвращает значение #t. И наоборот, если мы решаем, что еще не завершили обработку сигнала (или что-то сделали, но родительский объект все еще нуждается в уведомлении), можно вернуть #f для продолжения процесса. Таково поведение поумолчанию, но при написании кода почти всегда лучше все объявлять явно: впоследствии это спасает от изрядной головной боли.

Устраиваем сцену

Теперь, разобравшись с обработкой сигналов, придумаем несколько генерирующих их объектов, а также сцену, где мы всё разместим.

 >>> stage=clutter.Stage()
 >>> stage.set_color(clutter.Color(0,0,0,255))
 >>> stage.set_size(200,200)
 >>> r=clutter.Rectangle()
 >>> r.set_size(60,30)
 >>> r.set_color(clutter.Color(255,0,0,255))
 >>> r.set_position(20,20)
 >>> r.set_reactive(True)

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

Теперь необходимо просто соединить события с обработчиками, определенными нами ранее, добавить прямоугольник на сцену и показать все, отрисовав в окне:

 >>> r.connect(‘enter-event’, entered)
 >>> r.connect(‘leave-event’,exited)
 >>> r.show()
 >>> stage.add(r)
 >>> stage.show_all()

Самые наблюдательные из вас могли уже спросить себя: как работает метод connect()? В конце концов, наши обработчики событий желают знать актера и событие, а все, что мы сделали – это соединили сигналы с соответствующим именем функции (пара скобок после entered или exited не нужна – это не вызов!).


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

Попробуйте. Нам не нужно входить в основной цикл Clutter, чтобы заставить работать обработчики событий нашего скрипта: просто подвигайте мышью в окне над прямоугольником – и увидите, что цвет меняется. Отведите мышь, и он сменит цвет на другой. Ах, эта магия событий…

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


Решение заключается в том, чтобы ухитриться заставить наш обработчик работать с несколькими событиями. Кстати, он уже работает с несколькими актерами – посмотрите, ведь код ссылается на актера, подающего сигнал, а не на конкретный объект r. То есть, если мы создадим полторы дюжины прямоугольников, все они будут вести себя одинаково.

Давайте начнем заново, в этот раз с целой стаей кнопок, на которые можно нажимать, но с единственным обработчиком. Теперь несколько проще написать скрипт в Kate или Gedit и запустить его как обычное приложение:

 >>> stage.remove(r)
 >>> button =[]
 >>> def handler(actor,event):
 ... print event,actor
 ... print event.type.value_nick
 ... if event.type.value_nick==’enter’:
 ...   actor.set_color(clutter.Color(0,0,255,255))
 ... elif event.type.value_nick==’leave’:
 ...   actor.set_color(clutter.Color(255,0,255,255))
 ... elif event.type.value_nick==’button­press’:
 ...   actor.set_color(clutter.Color(255,255,255,255))
 ... return#t’
 ...
 >>> stage.set_size(300,200)
 >>> for i in range(4):
 ... r=clutter.Rectangle()
 ... r.set_size(50,30)
 ... r.set_color(clutter.Color(255,0,0,255))
 ... r.set_position(25+(i*60),150)
 ... r.set_reactive(True)
 ... r.connect(‘enter­event’, handler)
 ... r.connect(‘leave­event’,handler)
 ... r.connect(‘button­press­event’,handler)
 ... r.show()
 ... stage.add(r)
 ... button.append(r)
 ...


Здесь есть только два момента, достойных упоминания, поскольку они отличаются от того, что мы уже делали. Во-первых, обработчик теперь реагирует на все события от всех кнопок. Пока примем, что все кнопки ведут себя одинаково (к более изощренному сценарию мы перейдем позже), и все, что нам следует сделать – это сообразить, какое событие обрабатывает функция. Поскольку объект event автоматически передается в виде параметра, то необходимо просто рассмотреть его свойства (их много, но event.name_nick короткое и делает код более читабельным). В Python отсутствует конструкция ‘case … switch’ – однако от использования if и elif мы здесь потеряем не много.

Во-вторых, мы делаем нечто слегка ненормальное. В цикле for мы создаем прямоугольник r и наделяем его свойствами, а затем мы возвращаемся и делаем все по новой – а не затираем ли мы значение в r? И да, и нет. В самом конце цикла мы добавляем объект-прямоугольник к нашему списку кнопок методом append(). На самом деле, r – не сам объект, а лишь его адрес. Прямоугольник будет уничтожен только в том случае, если на него не останется действующих ссылок. В данном случае ссылка есть: она в нашем списке кнопок. При следующем проходе цикла создается еще один прямоугольник, и в r помещается новый адрес.

Таким образом, каждая кнопка сохраняется, и все они различны. Другой способ сделать это – ссылаться на этапе создания на элементы в списке кнопок; но тогда следует заранее заполнить его корректным числом элементов или добавлять пустые объекты в начале цикла. Этот путь проще, хотя и заставляет делать двойную работу.

Хорошее поведение

Clutter нацелен на создание шикарных графических интерфейсов, так почему бы не сделать так, чтобы кнопки проявлялись и плавно исчезали при перемещении над ними мыши? Это пригодится для медиа-проигрывателя, в котором обычно вы смотрите на экран, но когда хотите чем-то управлять, появляются кнопки. Мы можем легко добавить их при помощи поведений [behaviour] Clutter. Поведение похоже на сохраненную анимацию, увязанную с событиями временной шкалы. Временная шкала, как мы видели в предыдущих учебниках, это просто механизм прерываний. Clutter позволяет подключать поведения к объектам, а затем вызывать анимацию, запуская шкалу времени.

Я знаю, о чем вы думаете: не проще ли было использовать метод actor.animate() для изменения прозрачности объекта? Проще-то проще, да только он не работает. В темных глубинах Clutter кроется разрушительная ошибка, не позволяющая использовать простой метод animate() для прозрачности. Если вы все же попытаетесь, то получите несколько предупреждений, хотя код все же будет работать, и любая анимация просто превратится в черноту. Поэтому поведение объектов – наше все.

 >>> timelinefadein = clutter.Timeline(duration=600)
 >>> timelinefadeout = clutter.Timeline(duration=600)
 >>> alpha = clutter.Alpha(timelinefadein, clutter.EASE_IN_SINE)
 >>> behaviour = clutter.BehaviourOpacity(0x0, 0xc0, alpha)
 >>> alpha2 = clutter.Alpha(timelinefadeout, clutter.EASE_OUT_SINE)
 >>> behaviour2 = clutter.BehaviourOpacity(0xc0, 0, alpha2)

Мы можем подключать оба поведения к кнопкам по мере их создания и вызывать их из нашего обработчика событий. То есть проявляться и исчезать будут все кнопки, а не только та, над которой завис курсор мыши.

Объекты BehaviourOpacity принимают три параметра – alpha-объект (контролирующий значения анимации от кадра к кадру) и начальную и конечную непрозрачности. Здесь мы привели их в шестнадцатеричном формате; обычно это лучше, поскольку некоторые операции над альфа-эффектами весьма требовательны к получаемым значениям, а в таком варианте вы будете уверены, что оно преобразуется как 8‑битное целое.

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

Итак, большой вопрос в том, какие чудеса мы извлечем из волшебной корзины Python, чтобы заставить каждую кнопку вести себя по-своему без добавления хрентиллиона строк кода в наш обработчик событий? А позвольте привлечь ваше внимание к экспонату A:

 >>> example=clutter.Rectangle()
 >>> example.set_size(60,20)
 >>> example.MyMadeUpProperty = 8
 >>> example.MyMadeUpProperty
 8
 >>>

Вот вам и магия Python. Без всякой мороки с изобретением новых классов и тому подобного, мы можем динамически добавлять свойства к существующим объектам. Итак, хотя актеры Clutter не имеют соответствующего места для хранения свойства ‘при нажатии кнопки выполнить’, мы просто можем добавить его позднее. Актер rectangle будет вести себя так же, как обычный прямоугольник, но мы можем приписать к нему любые невероятные выкрутасы, а то и полезные вещи.

Нажмите кнопку

Это приводит нас к некоторым дополнительным ценным свойствам Python. Почти все в мире Python – просто объекты, включая методы и функции. В сущности, метод – всего лишь объект со свойством __call__. Радоваться тут особо нечему, но зато можно выполнять следующее:


 >>> dir
 <built-in function dir>
 >>> dir()
 [‘__builtins__’, ‘__doc__’, ‘__name__’, ‘__package__’]
 >>> x=dir
 >>> x
 <built-in function dir>
 >>> x()
 [‘__builtins__’, ‘__doc__’, ‘__name__’, ‘__package__’, ‘x’]
 >>> x(x)
 [__call__’, ‘__class__’, ‘__cmp__’, ‘__delattr__’, ‘__doc__’, ‘__
 eq__’, ‘__format__’, ‘__ge__’,
 ‘__getattribute__’, ‘__gt__’, ‘__ hash__’, ‘__init__’, ‘__le__’, ‘__
 lt__’, ‘__module__’,
 ‘__name__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’,
 ‘__repr__’, ‘__self__’,
 ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__ subclasshook__’]

Dir – встроенная функция Python, с которой мы уже встречались; в основном она используется для интроспекции и сообщает, что есть в объекте. Присваивая переменной x значение dir (заметьте, что мы не используем скобки в конце), мы создаем ссылку на эту функцию. В результате x будет вести себя как dir. Это действительно dir, просто с другой меткой. Теперь вы можете вызвать x так же, как вызывается dir.

Слияние двух этих фрагментов с нашим существующим кодом Clutter означает, что мы сможем определить методы для выполнения действий, а затем присвоить их новым свойствам, которые добавим к уже созданным объектам-кнопкам. Наш класс-обработчик все еще не требует выполнения чего-то особенного – при щелчке на кнопке он просто вызывает ее свойство-метод ‘action’ или как мы там пожелаем его назвать. Вам, возможно, кажется, что мы немного увлеклись идеей не писать дополнительный код; тут и правда экономится несколько строк, но на самом деле этот способ обработки объектов реализуется для лучшей читабельности и сопровождаемости участка кода. Функция handler() – лишь эффективная часть конструкции, или, скажем, коммутатор, соединяющий компоненты вместе при необходимости.

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

Чувак! А где же карта?

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

Персональные инструменты
купить
подписаться
Яндекс.Метрика