events_ru - oxygine/oxygine-framework GitHub Wiki

События

Oxygine реализует модель событий взятую из Adobe Flash (as3). Эта статья основана на стать о событиях из as3: http://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html

класс EventDispatcher

Обработка событий в Oxygine сильно зависит от класса EventDispatcher.

Для тех кто не знаком с классом EventDispatcher, основная идея такова: Вначале вы определяет функции/методы, так называемые "обработчики событий" (event handler), которые реагирую на различные события. Затем вы связываете эти функции с событиями используя метод addEventListener(), который вызывается из объекта, который будет получать сообщение.

Код использующий EventDispatcher выглядит примерно так:

void clickHandler(Event*) {}
submitButton->addEventListener(TouchEvent::CLICK, CLOSURE(this, &SomeClass::clickHandler));

Используя EventDispatcher, вы можете вызвать removeEventListener(). Этот метод удаляет обработчик события, которое вы задали в методе addEventListener.

submitButton->removeEventListener(TouchEvent::CLICK, CLOSURE(this, &SomeClass::clickHandler));

Вы можете использовать лямбда-функции из C++11 (std::function и lambda):

submitButton->addEventListener(TouchEvent::CLICK, [](Event*){
	logs::messageln("button clicked");
});

это медленнее чем использование макроса CLOSURE

Где находится EventDispatcher?

Вы могли заметить что вышеупомянутый код нигде явно не обращается к классу EventDispatcher. Вообще, вы вряд-ли когда-нибудь будете использовать EventDispatcher напрямую в вашем коде. В Oxygine, EventDispatcher это базовый класс, от которого наследуются другие классы-актёры чтобы иметь доступ к addEventListener и другим методам класса EventDispatcher.

Методы EventDispatcher

Вот краткое описание методов EventDispatcher:

int addEventListener(eventType type, EventCallback callback); 
void removeEventListener(eventType type, EventCallback callback);
void dispatchEvent(Event *event);
  • addEventListener: Добавляет функцию обработки события, которая вызовется когда произойдёт указанное событие.
  • removeEventListener: Убирает функцию обработки события, из списка обработчиков. Чтобы убрать нужный обработчик, необходимо передать точно такие же два параметра, которые были переданы в метод addEventListener.

также есть другие упрощённые версии метода removeEventListener

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

Пример 1: Щелчок по квадрату

Для простого примера возьмём щелчок по квадрату на экране с именем "box". Задача этого примера - обработать это событие таким образом чтобы в окне "Вывод" появлялся текст "click" когда по квадрату щелкнули мышкой.

class Root : public ColorRectSprite
{
public:
	void clickHandler(Event*)
	{
		logs::messageln("click");
	}

	Root()
	{
		setSize(300, 200);
		setPosition(getStage()->getSize() / 2 - getSize() / 2);
		setColor(Color::Silver);
		spColorRectSprite box = new ColorRectSprite;
		box->setColor(Color::Salmon);
		box->setPosition(50, 50);
		box->setSize(100, 100);
		addChild(box);

		box->addEventListener(TouchEvent::CLICK, CLOSURE(this, &Root::clickHandler));
	}
};

Для запуска примера, сделайте следующее:

  • откройте пример HelloWorld
  • вставьте этот код в example.cpp
  • добавьте следующий код в функцию example_init:
getStage()->addChild(new Root);

Давайте рассмотрим код. Первый шаг это определить обработчик события. Как и для всех событий, это функция которая принимает один параметр - указатель на объект события, который автоматически передаётся при вызове из EventDispatcher. После этого, эта функция устанавливается в качестве обработчика события TouchEvent::CLICK для объекта "box" с помощью вызова метода addEventListener. Так как объект "box" это экземпляр класса ColorRectSprite, он наследуется от EventDispatcher и имеет доступ ко всем методам EventDispatcher, включая addEventListener.

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

Запуск этого примера покажет квадрат, при щелчке на который, выведется слово "click".

Event propagation and phases

Although you might not have realized it in the previous example, the event that took place as a result of the box being clicked actually affects many different objects, not just the object being clicked. Event handling is the support for event propagation—the transference of a single event applying to multiple objects. Each of those objects receives the event, instead of just the object in which the event originated.

With event propagation you're dealing with three "phases" of an event. Each phase represents a path or the location of an event as it works itself through the display objects in Oxygine that relate to that event. The three phases of an event are capturing, at target, and bubbling:

  • Capturing phase: This represents the parent objects of the target object from which the event originated. Any propagated event starts with the topmost parent (stage) and works down the display object hierarchy until reaching the original target.
  • At target phase: The target phase is the phase where the event is at the target object or the object from which the event originated. Unlike the capturing and bubbling phases, this phase always relates to only one object, the target object.
  • Bubbling phase: When an event "bubbles" it follows the reverse path of the capturing phase and works its way back up the parent hierarchy of the target object until reaching the top-most parent or stage. Not all propagated events (and not all events propagate) go through each phase, however. If the Stage object, for example, receives an event, there will only be an at target phase since there are no objects beyond the stage for the capturing or bubbling phases to take place.

As the event makes its way though each phase and each object within those phases, it calls all of the listener functions that were added for that event. This means that clicking on the box doesn't limit the event to the box; the Stage also receives the event.

You can see how this all works more clearly by adding more listeners to our example.

Example 2: One box, many listeners

To see how a single mouse click propagates through many objects within the display list hierarchy, you can add additional listeners to receive the event for each of those objects it affects.

In this example we will add listeners for Stage, Root, and box.

Clicking the box propagates the events throughout all specified listeners for all phases

class Root : public ColorRectSprite
{
public:
	void boxClick(Event*)
	{
		logs::messageln("box click");
	}
	void rootClick(Event*)
	{
		logs::messageln("root click");
	}
	void stageClick(Event*)
	{
		logs::messageln("stage click");
	}
	Root()
	{
		setSize(300, 200);
		setPosition(getStage()->getSize() / 2 - getSize() / 2);
		setColor(Color::Silver);
		spColorRectSprite box = new ColorRectSprite;
		box->setColor(Color::Salmon);
		box->setPosition(50, 50);
		box->setSize(100, 100);
		addChild(box);
	
		getStage()->addEventListener(TouchEvent::CLICK, CLOSURE(this, &Root::stageClick));
		box->addEventListener(TouchEvent::CLICK, CLOSURE(this, &Root::boxClick));
		this->addEventListener(TouchEvent::CLICK, CLOSURE(this, &Root::rootClick));
	}
};

Test this example and click around to see the results. Clicking on the box should give you the following output:

box click
root click
stage click

Try clicking anywhere off of the box and you get an output of:

root click
stage click

or:

stage click

check also example/Demo/Touches

Event targeting

All events, like mouse clicks, start off in Oxygine with the process of event targeting. This is the process by which Oxygine determines which object is the target of the event (where the event originates). In the previous examples we've seen how Oxygine was able to determine whether or not you clicked on the box or the Stage. For every mouse event, the event references one object—the object of highest arrangement capable of receiving the event.

All display objects are, by default, enabled to receive touch events. This means that even if no event handlers have been assigned to a particular actor, it will still be targeted for an event when clicked, preventing anything below it from receiving events.

To change this behavior use the setTouchEnabled setter. Setting it to false will disable an acotr instance from receiving touch events and allows other instances below it to be targeted for touch events:

sprite->setTouchEnabled(false);

Event objects

Event objects are the objects listener functions receive as an argument when called during the occurrence of an event.

The event objects received by listener method are always of the type Event but can also be a subclass of Event rather than specifically being an Event instance. Common subclasses include TouchEvent for events associated with the mouse. Each class also contains the event type constants used for listening to related events, e.g. TouchEvent::CLICK.

The stage

The stage represents the topmost container of all actors within a application.

Additionally, stage targeting for mouse events does not depend on stage contents as is the case with other objects. With the box example, you have the basic hierarchy of stage > root > box. To click on the box and have Oxygine target the box for the click event, you need to click on the shape that makes up the box. Similarly, to click on the root object, you need to click on its contents, or the box instance. Clicking anywhere else will not be clicking on root since root consists only of the box. For the stage, however, you can click on the stage by clicking anywhere on the movie because the stage exists everywhere as a persistent background for the application, even if there is no other content on the stage.

Event properties

Here is the basic rundown of common Event properties:

eventType type;
Phase phase;
bool bubbles;
spEventDispatcher target;
spEventDispatcher currentTarget;
  • type: The type of event being handled. This is the same as the first parameter used to add the function as a listener in addEventListener. Ex: TouchEvent::CLICK.

  • eventPhase: The current phase of the event. This is provided as a number that relates to the constants Event::phase_target, and Event::phase_bubbling, depending on which phase the listener is being called.

  • bubbles: Indicates whether or not the event is an event that bubbles. This does not mean that the event went through or is going through a bubbles phase, but rather it is a kind of event that can. When clicking just the stage in the previous example, the event did not bubble because stage was the only target. However, bubbles would still be true since the event was a mouse click.

  • target: The object which Oxygine targeted for the event. This is the object that would receive the event in the at target phase.

  • currentTarget: Refers to the object that is currently called by the listener when the event occurs. This is the same object on which addEventListener was called from when the listener was added. These properties can be useful in determining specific actions that need to be taken for various events.

Event methods

There are also a few useful methods associated with Event objects. They include but are not limited to:

stopPropagation()
stopImmediatePropagation()

Here is a description of what some of these event methods can do:

  • stopPropagation(): When called within an event listener for an event that bubbles, it will stop the propagation of the event to the remaining objects which would otherwise receive the event within the current phase and any remaining phases.

  • stopImmediatePropagation(): When called within an event listener for an event that bubbles, it will stop the propagation of the event within the current object as well as in remaining objects . This works much like stopPropagation except stopPropagation will not prevent additional events in the current object to be called (if there is more than one listener listening for the same event in the same object).

Disable specific events within a container

As I mentioned earlier, touch events are inherently enabled for all interactive objects in Oxygine. You can disable touch interaction by setting their setTouchEnabled property to false.

Additionally, there's a similar property for actors that allow you to disable touch events for all children of that object - setTouchChildrenEnabled. By setting touchChildren to false, you can effectively prevent the mouse from being enabled for all instances within any actor.

Customizing events

You can create your own events. This encompasses dispatching new or existing events, creating new types of events, and defining new event classes (based on the Event class) whose instances are to be passed to event handlers listening for that event.

To dispatch events manually, you use the dispatchEvent method of EventDispatcher. When calling dispatchEvent you pass an event object that describes the event being dispatched. This event then makes its way through all valid targets (multiple if propagated) causing any event handlers assigned as listeners to those targets to be called (if they are listening for that particular type of event). When the handlers are called, they each receive the event object passed to dispatchEvent:

int type = 123;
Event event(type);
target->dispatchEvent(&event);

New event instances are created with a type parameter and optional bubbles. By default, at least in the Event class, bubbles is false if not explicitly passed in as true. Subclasses of the Event class like TouchEvent accept even more parameters, and in the case of the TouchEvent class, the default setting for bubbles is true.

You can create your own event classes by extending the core Event class. These custom subclasses can be used in dispatching your own custom events and have properties of your own choosing.

Custom event example

Custom events are useful for indicating events which are not inherently recognized within Oxygine. By making your own Event classes, you can provide handlers for those events by specifying additional information relating to your custom event.

class AchieveEarnedEvent: public Event
{
public:
	enum {EVENT = eventID('A','c','E','r')};
	string achievementID;
	AchieveEarnedEvent(string id):Event(EVENT), achievementID(id){}
};


**eventID** above is a preprocessor command to easy define unique integer value: 

```cpp
#define eventID(a,b,c,d) ( ((unsigned int)a) | (((unsigned int)b)<< 8) | (((unsigned int)c)<<16) | (((unsigned int)d)<<24))

Listen custom created event:

void SomeClass::achieveListener(Event *ev)
{
	AchieveEarnedEvent* event = static_cast<AchieveEarnedEvent*>(ev);
	logs::messageln("achievement earned: %s", event->achievementID.c_str());
}

target->addEventListener(AchieveEarnedEvent::EVENT, CLOSURE(this, &SomeClass::achieveListener));

Dispatch event:

AchieveEarnedEvent event("awesome");
target->dispatchEvent(&event);

examples/Game/Part4 has custom HiddenEvent in the class Scene