You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
9.6 KiB
Python

# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
'''Application-wide functionality.
Most applications need only call `run` after creating one or more windows
to begin processing events. For example, a simple application consisting of
one window is::
from pyglet import app
from pyglet import window
win = window.Window()
app.run()
To handle events on the main event loop, instantiate it manually. The
following example exits the application as soon as any window is closed (the
default policy is to wait until all windows are closed)::
event_loop = app.EventLoop()
@event_loop.event
def on_window_close(window):
event_loop.exit()
:since: pyglet 1.1
'''
__docformat__ = 'restructuredtext'
__version__ = '$Id: __init__.py 2140 2008-07-27 04:15:52Z Alex.Holkner $'
import sys
import weakref
from pyglet import clock
from pyglet import event
_is_epydoc = hasattr(sys, 'is_epydoc') and sys.is_epydoc
class WeakSet(object):
'''Set of objects, referenced weakly.
Adding an object to this set does not prevent it from being garbage
collected. Upon being garbage collected, the object is automatically
removed from the set.
'''
def __init__(self):
self._dict = weakref.WeakKeyDictionary()
def add(self, value):
self._dict[value] = True
def remove(self, value):
del self._dict[value]
def __iter__(self):
for key in self._dict.keys():
yield key
def __contains__(self, other):
return other in self._dict
def __len__(self):
return len(self._dict)
#: Set of all open displays. Instances of `Display` are automatically added
#: to this set upon construction. The set uses weak references, so displays
#: are removed from the set when they are no longer referenced.
#:
#: :type: `WeakSet`
displays = WeakSet()
#: Set of all open windows (including invisible windows). Instances of
#: `Window` are automatically added to this set upon construction. The set
#: uses weak references, so windows are removed from the set when they are no
#: longer referenced or are closed explicitly.
#:
#: :type: `WeakSet`
windows = WeakSet()
class BaseEventLoop(event.EventDispatcher):
'''The main run loop of the application.
Calling `run` begins the application event loop, which processes
operating system events, calls `pyglet.clock.tick` to call scheduled
functions and calls `pyglet.window.Window.on_draw` and
`pyglet.window.Window.flip` to update window contents.
Applications can subclass `EventLoop` and override certain methods
to integrate another framework's run loop, or to customise processing
in some other way. You should not in general override `run`, as
this method contains platform-specific code that ensures the application
remains responsive to the user while keeping CPU usage to a minimum.
'''
#: Flag indicating if the event loop will exit in the next iteration.
#: This is
has_exit = False
def run(self):
'''Begin processing events, scheduled functions and window updates.
This method returns when `has_exit` is set to True.
Developers are discouraged from overriding this method, as the
implementation is platform-specific.
'''
raise NotImplementedError('abstract')
def _setup(self):
global event_loop
event_loop = self
# Disable event queuing for dispatch_events
from pyglet.window import Window
Window._enable_event_queue = False
# Dispatch pending events
for window in windows:
window.switch_to()
window.dispatch_pending_events()
def _idle_chance(self):
'''If timeout has expired, manually force an idle loop.
Called by window that have blocked the event loop (e.g. during
resizing).
'''
def idle(self):
'''Called during each iteration of the event loop.
The method is called immediately after any window events (i.e., after
any user input). The method can return a duration after which
the idle method will be called again. The method may be called
earlier if the user creates more input events. The method
can return `None` to only wait for user events.
For example, return ``1.0`` to have the idle method called every
second, or immediately after any user events.
The default implementation dispatches the
`pyglet.window.Window.on_draw` event for all windows and uses
`pyglet.clock.tick` and `pyglet.clock.get_sleep_time` on the default
clock to determine the return value.
This method should be overridden by advanced users only. To have
code execute at regular intervals, use the
`pyglet.clock.schedule` methods.
:rtype: float
:return: The number of seconds before the idle method should
be called again, or `None` to block for user input.
'''
dt = clock.tick(True)
# Redraw all windows
for window in windows:
if window.invalid:
window.switch_to()
window.dispatch_event('on_draw')
window.flip()
# Update timout
return clock.get_sleep_time(True)
def exit(self):
'''Safely exit the event loop at the end of the current iteration.
This method is convenience for setting `has_exit` to ``True``.
'''
self.has_exit = True
def on_window_close(self, window):
'''Default window close handler.'''
if not windows:
self.exit()
if _is_epydoc:
def on_window_close(window):
'''A window was closed.
This event is dispatched when a window is closed. It is not
dispatched if the window's close button was pressed but the
window did not close.
The default handler calls `exit` if no more windows are open. You
can override this handler to base your application exit on some
other policy.
:event:
'''
def on_enter():
'''The event loop is about to begin.
This is dispatched when the event loop is prepared to enter
the main run loop, and represents the last chance for an
application to initialise itself.
:event:
'''
def on_exit():
'''The event loop is about to exit.
After dispatching this event, the `run` method returns (the
application may not actually exit if you have more code
following the `run` invocation).
:event:
'''
BaseEventLoop.register_event_type('on_window_close')
BaseEventLoop.register_event_type('on_enter')
BaseEventLoop.register_event_type('on_exit')
#: The global event loop. Set to the correct instance when an `EventLoop` is
#: started.
#:
#: :type: `EventLoop`
event_loop = None
def run():
'''Begin processing events, scheduled functions and window updates.
This is a convenience function, equivalent to::
EventLoop().run()
'''
EventLoop().run()
def exit():
'''Exit the application event loop.
Causes the application event loop to finish, if an event loop is currently
running. The application may not necessarily exit (for example, there may
be additional code following the `run` invocation).
This is a convenience function, equivalent to::
event_loop.exit()
'''
if event_loop:
event_loop.exit()
if _is_epydoc:
EventLoop = BaseEventLoop
EventLoop.__name__ = 'EventLoop'
del BaseEventLoop
else:
# Permit cyclic import.
import pyglet
pyglet.app = sys.modules[__name__]
if sys.platform == 'darwin':
from pyglet.app.carbon import CarbonEventLoop as EventLoop
elif sys.platform in ('win32', 'cygwin'):
from pyglet.app.win32 import Win32EventLoop as EventLoop
else:
from pyglet.app.xlib import XlibEventLoop as EventLoop