Skip to content

Commit

Permalink
documentation: rework event dispatching section
Browse files Browse the repository at this point in the history
  • Loading branch information
benmoran56 committed Jun 12, 2023
1 parent b99cb11 commit 53bb16b
Showing 1 changed file with 64 additions and 41 deletions.
105 changes: 64 additions & 41 deletions doc/programming_guide/events.rst
Original file line number Diff line number Diff line change
@@ -1,78 +1,101 @@
.. _guide_events:

Dispatching Events
==================
Event dispatching & handling
============================

The :py:mod:`pyglet.window`, :py:mod:`pyglet.media`, :py:mod:`pyglet.app`,
:py:mod:`pyglet.text`, :py:mod:`pyglet.input` and other modules make use
of a consistent event pattern. This provides several ways to attach event
handlers to objects. You can also reuse this pattern in your own
classes easily, by subclassing :py:class:`~pyglet.event.EventDispatcher`.
The :py:mod:`pyglet.event` module provides a framework for uniformly dispatching
and handling events. For our purposes, an "event dispatcher" is an object that has
events it needs to notify other objects about, and an "event handler" is some code
(a function or method) that can be registered (or "attached") to receive those events.

Event Dispatchers are created by subclassing the :py:class:`~pyglet.event.EventDispatcher`
base class. Many of pyglet's built-in modules, such as :py:mod:`pyglet.window`,
:py:mod:`pyglet.media`, :py:mod:`pyglet.app`, :py:mod:`pyglet.text`, :py:mod:`pyglet.input`,
:py:mod:`pyglet.gui` and others make use of this pattern. You can also reuse this in
your own classes easily.

Even handlers are simply functions or methods that are written to accept the same
arguments as the dispatched event. Event handlers can be registered or unregistered
during runtime. More than one handler can be registered to receive the same events,
which is described in the following sections. Event dispatchers can _optionally_ have
default handlers for some of their events. Your own handlers can replace these entirely,
or just be added on.

Throughout this documentation, an "event dispatcher" is an object that has
events it needs to notify other objects about, and an "event handler" is some
code that can be attached to a dispatcher.

Setting event handlers
----------------------

An event handler is simply a function with a formal parameter list
corresponding to the event type. For example, the
:py:meth:`pyglet.window.Window.on_resize` event has the parameters
``(width, height)``, so an event handler for this event could be written as::
For an example, lets look at the :py:class:`~pyglet.window.Window` class.
:py:class:`~pyglet.window.Window` subclasses :py:class:`~pyglet.event.EventDispatcher`
and, being an Window, has a variety of different events which it dispatches.
For instance, the :py:meth:`pyglet.window.Window.on_resize` event. Every time a
resizeable Window is resized (and once when first created), this event is dispatched
with two parameters: ``(width, height)``. Therefore, an event handler for this event
should be written to accept these two values. For example::

def on_resize(width, height):
pass

The :py:class:`~pyglet.window.Window` class subclasses
:py:class:`~pyglet.event.EventDispatcher`, which enables it to dispatch
its own events. There are a few different ways in which event handlers
can be attached to recieve them. The simplest way is to directly attach the
event handler to the corresponding attribute on the object. This will
completely replace the default event handler::
There are a few different ways in which event handlers can be attached to recieve them.
The simplest way is to directly attach the event handler to the corresponding attribute
on the object. This will completely replace the default event handler::

window = pyglet.window.Window()

def on_resize(width, height):
pass
# Set some custom projection

window.on_resize = on_resize

If you don't want to replace the default event handler, but instead want to
add an additional one, pyglet provides a shortcut using the
:py:class:`~pyglet.event.EventDispatcher.event` decorator.
Your custom event handler will run, followed by the default event handler::
Sometimes replacing the default handler is desired, but not in all cases.
For example the default `Window.on_resize` handler is responsible for setting up a
orthographic 2D projection for drawing graphics. If you replace it entirely, you must
also handle setting the projection yourself.

Another way to replace a default event handler is when subclassing pyglet objects.
This is common do do with Window class, as shown in :ref:`guide_subclassing-window`.
If your methods have the same name as the default event, they will be replaced::

class MyWindow(pyglet.window.Window):
def on_resize(self, width, height):
# set a custom projection there


You can of course still call the default handler with `super()`, and then add
your custom code before/after that::

class MyWindow(pyglet.window.Window):

def on_resize(self, width, height):
super().on_resize(width, height)
# do something else

The event decorator
^^^^^^^^^^^^^^^^^^^

Instead of replacing default handlers, you can just also add an additional handler.
pyglet provides a shortcut using the :py:class:`~pyglet.event.EventDispatcher.event`
decorator. Your custom event handler will run, followed by the default event handler::

window = window.Window()

@window.event
def on_resize(width, height):
pass
print(f"Window was resized to: {width}x{height}")

or if your handler has a different name::
or if your handler has a different name, pass the event name to the decorator::

@window.event('on_resize')
def my_resize_handler(width, height):
pass

In some cases, replacing the default event handler may be desired.
For example, the default :py:meth:`pyglet.window.Window.on_resize` event
sets up a 2D orthographic OpenGL projection. If you wish to use another
OpenGL projection, such as for a 3D scene, then you will likely want
to replace this with your own custom event handler.

In most simple cases, the :py:class:`~pyglet.event.EventDispatcher.event`
decorator is most convienent. One limitation of using the decorator,
however, is that you can only add one additional event handler.
If you want to add multiple additional event handlers, the next section
describes how to accomplish that.

As a quick note, as shown in :ref:`guide_subclassing-window`,
you can also replace default event handlers by subclassing the event
dispatcher and adding the event handler as a method::

class MyWindow(pyglet.window.Window):
def on_resize(self, width, height):
pass

Stacking event handlers
-----------------------
Expand All @@ -81,7 +104,7 @@ It is often convenient to attach more than one event handler for an event.
:py:class:`~pyglet.event.EventDispatcher` allows you to stack event handlers
upon one another, rather than replacing them outright. The event will
propagate from the top of the stack to the bottom, but can be stopped
by any handler along the way.
by any handler along the way by returning `pyglet.event.EVENT_HANDLED`.

To push an event handler onto the stack,
use the :py:meth:`~pyglet.event.EventDispatcher.push_handlers` method::
Expand Down Expand Up @@ -183,7 +206,7 @@ but exposes a public interface for creating and dispatching your own events.
The steps for creating an event dispatcher are:

1. Subclass :py:class:`~pyglet.event.EventDispatcher`
2. Call the :py:meth:`~pyglet.event.EventDispatcher. register_event_type`
2. Call the :py:meth:`~pyglet.event.EventDispatcher.register_event_type`
class method on your subclass for each event your subclass will recognise.
3. Call :py:meth:`~pyglet.event.EventDispatcher. dispatch_event` to create and
dispatch an event as needed.
Expand Down

0 comments on commit 53bb16b

Please sign in to comment.