use master of pyglet to fix scale issue

This commit is contained in:
shenjack 2023-03-31 00:10:49 +08:00
parent 495ece2273
commit 4daec5acc2
5 changed files with 440 additions and 618 deletions

View File

@ -179,8 +179,8 @@ class ClientWindow(Window):
self.input_box = InputBox(x=50, y=30, width=300,
batch=self.label_batch, text='') # 实例化
self.input_box.push_handlers(self)
self.input_box.push_handler('on_commit', self.on_input)
self.push_handlers(self.input_box)
self.input_box.set_handler('on_commit', self.on_input)
self.set_handlers(self.input_box)
self.input_box.enabled = True
# 设置刷新率
pyglet.clock.schedule_interval(self.draw_update, float(self.SPF))

View File

@ -1,38 +1,3 @@
# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# Copyright (c) 2008-2020 pyglet contributors
# 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.
# ----------------------------------------------------------------------------
"""Event dispatch framework.
All objects that produce events in pyglet implement :py:class:`~pyglet.event.EventDispatcher`,
@ -44,155 +9,103 @@ Event types
For each event dispatcher there is a set of events that it dispatches; these
correspond with the type of event handlers you can attach. Event types are
identified by their name, for example, ''on_resize''.
If you are creating a new class which implements
:py:class:`~pyglet.event.EventDispatcher`, or want to add new events
to an existing dispatcher, you must call `EventDispatcher.register_event_type`
for each event type:
class MyDispatcher(pyglet.event.EventDispatcher):
# ...
MyDispatcher.register_event_type('on_resize')
identified by their name, for example, ''on_resize''. If you are creating a
new class which implements :py:class:`~pyglet.event.EventDispatcher`, you must call
`EventDispatcher.register_event_type` for each event type.
Attaching event handlers
========================
An event handler is simply a function or method, that is called when system or
program event happens. There are several ways to add a handler for an event.
An event handler is simply a function or method. You can attach an event
handler by setting the appropriate function on the instance::
When the dispatcher object is available as a global variable, it is convenient
to use the `event` decorator:
def on_resize(width, height):
# ...
dispatcher.on_resize = on_resize
@window.event
There is also a convenience decorator that reduces typing::
@dispatcher.event
def on_resize(width, height):
# ...
Here `window` is a variable containing an instance of `pyglet.window.Window`,
which inherits from `EventDispatcher` class. This decorator assumes that
the function is named after the event. To use the decorator with a function with
another name, pass the name of the event as the argument for the decorator:
You may prefer to subclass and override the event handlers instead::
@window.event('on_resize')
def my_resize_handler(width, height);
# ...
The most universal way to add an event handler is to call the `push_handlers`
method on the dispatcher object:
window.push_handlers(on_resize)
window.push_handlers(on_resize=my_handler)
window.push_handlers(on_resize=obj.my_handler)
window.push_handlers(obj)
This methods accepts both positional and keyword parameters. In case of keyword
arguments, the name of the event matches the name of the argument. Otherwise,
the name of the passed function or method is used as the event name.
If an object is passed as a positional argument, all its methods that match
the names of registered events are added as handlers. For example:
class MyDispatcher(pyglet.event.EventDispatcher):
# ...
MyDispatcher.register_event_type('on_resize')
MyDispatcher.register_event_type('on_keypress')
class Listener(object):
def on_resize(self, w, h):
# ...
def on_keypress(self, key):
# ...
def other_method(self):
# ...
dispatcher = MyDispatcher()
listener = Listener()
dispatcher.push_handlers(listener)
In this example both `listener.on_resize` and `listener.on_keypress` are
registered as handlers for respective events, but `listener.other_method` is
not affected, because it doesn't correspond to a registered event type.
Finally, yet another option is to subclass the dispatcher and override the event
handler methods::
class MyDispatcher(pyglet.event.EventDispatcher):
class MyDispatcher(DispatcherClass):
def on_resize(self, width, height):
# ...
If both a parent class and the child class have a handler for the same event,
only the child's version of the method is invoked. If both event handlers are
needed, the child's handler must explicitly call the parent's handler:
Event handler stack
===================
class ParentDispatcher(pyglet.event.EventDispatcher):
def on_resize(self, w, h);
# ...
When attaching an event handler to a dispatcher using the above methods, it
replaces any existing handler (causing the original handler to no longer be
called). Each dispatcher maintains a stack of event handlers, allowing you to
insert an event handler "above" the existing one rather than replacing it.
class ChildDispatcher(ParentDispatcher):
def on_resize(self, w, h):
super().on_resize(w, h)
# ...
There are two main use cases for "pushing" event handlers:
Multiple handlers for an event
==============================
* Temporarily intercepting the events coming from the dispatcher by pushing a
custom set of handlers onto the dispatcher, then later "popping" them all
off at once.
* Creating "chains" of event handlers, where the event propagates from the
top-most (most recently added) handler to the bottom, until a handler
takes care of it.
A single event can be handled by multiple handlers. The handlers are invoked in
the order opposite to the order of their registration. So, the handler
registered last will be the first to be invoke when the event is fired.
Use `EventDispatcher.push_handlers` to create a new level in the stack and
attach handlers to it. You can push several handlers at once::
An event handler can return the value `pyglet.event.EVENT_HANDLED` to prevent
running the subsequent handlers. Alternatively if the handle returns
`pyglet.event.EVENT_UNHANDLED` or doesn't return an explicit value, the next
event handler will be called (if there is one).
dispatcher.push_handlers(on_resize, on_key_press)
Stopping the event propagation is useful to prevent a single user action from
being handled by two unrelated systems. For instance, in game using WASD keys
for movement, should suppress movement when a chat window is opened: the
"keypress" event should be handled by the chat or by the character
movement system, but not both.
If your function handlers have different names to the events they handle, use
keyword arguments::
The order of execution of event handlers can be changed by assigning them
priority. Default priority for all handlers is 0. If handler needs to be run
before other handlers even though it was added early, it can be assigned
priority 1. Conversely, a handler added late can be assigned priority -1 to be
run late.
dispatcher.push_handlers(on_resize=my_resize, on_key_press=my_key_press)
Priority can be assigned by passing the `priority` named parameter to
`push_handlers` method:
After an event handler has processed an event, it is passed on to the
next-lowest event handler, unless the handler returns `EVENT_HANDLED`, which
prevents further propagation.
window.push_handlers(on_resize, priority=-1)
To remove all handlers on the top stack level, use
`EventDispatcher.pop_handlers`.
It can also be specified by using the `@priority` decorators on handler
functions and methods:
Note that any handlers pushed onto the stack have precedence over the
handlers set directly on the instance (for example, using the methods
described in the previous section), regardless of when they were set.
For example, handler ``foo`` is called before handler ``bar`` in the following
example::
@pyglet.event.priority(1)
def on_resize(w, h):
# ...
dispatcher.push_handlers(on_resize=foo)
dispatcher.on_resize = bar
class Listener(object):
@pyglet.event.priority(-1)
def on_resize(self, w, h):
# ...
listener = Listener()
Dispatching events
==================
dispatcher.push_handlers(on_resize, listener)
pyglet uses a single-threaded model for all application code. Event
handlers are only ever invoked as a result of calling
EventDispatcher.dispatch_events`.
Removing event handlers
=======================
It is up to the specific event dispatcher to queue relevant events until they
can be dispatched, at which point the handlers are called in the order the
events were originally generated.
In most cases it is not necessary to remove event handlers manually. When
the handler is an object method, the event dispatcher keeps only a weak
reference to it. It means, that the dispatcher will not prevent the object from
being deleted when it goes out of scope. In that case the handler will be
silently removed from the list of handlers.
This implies that your application runs with a main loop that continuously
updates the application state and checks for new events::
while True:
dispatcher.dispatch_events()
# ... additional per-frame processing
Not all event dispatchers require the call to ``dispatch_events``; check with
the particular class documentation.
.. note::
This means the following example will not work, because the pushed object
will fall out of scope and be collected::
In order to prevent issues with garbage collection, the
:py:class:`~pyglet.event.EventDispatcher` class only holds weak
references to pushed event handlers. That means the following example
will not work, because the pushed object will fall out of scope and be
collected::
dispatcher.push_handlers(MyHandlerClass())
@ -202,50 +115,12 @@ silently removed from the list of handlers.
my_handler_instance = MyHandlerClass()
dispatcher.push_handlers(my_handler_instance)
When explicit removal of handlers is required, the method `remove_handlers`
can be used. Its arguments are the same as the arguments of `push_handlers`:
dispatcher.remove_handlers(on_resize)
dispatcher.remove_handlers(on_resize=my_handler)
dispatcher.remove_handlers(on_resize=obj.my_handler)
dispatcher.remove_handlers(obj)
When an object is passed as a positional parameter to `remove_handlers`, all its
methods are removed from the handlers, regardless of their names.
Dispatching events
==================
pyglet uses a single-threaded model for all application code. Normally event
handlers are invoked while running an event loop by calling
pyglet.app.run()
or
event_loop = pyglet.app.EventLoop()
event_loop.run()
Application code can invoke events directly by calling the method
`dispatch_event` of `EventDispatcher`:
dispatcher.dispatch_event('on_resize', 640, 480)
The first argument of this method is the event name, that has to be previously
registered using `register_event_type` class method. The rest of the arguments
are pass to event handlers.
The handlers of an event fired by calling `dispatch_event` are called directly
from this method. If any of the handlers returns `EVENT_HANDLED`, then
`dispatch_event` also returns `EVENT_HANDLED` otherwise (or if there weren't
any handlers for a given event) it returns `EVENT_UNHANDLED`.
"""
import inspect as _inspect
from functools import partial as _partial
from weakref import WeakMethod as _WeakMethod
import inspect
from functools import partial
from weakref import WeakMethod
EVENT_HANDLED = True
EVENT_UNHANDLED = None
@ -257,36 +132,17 @@ class EventException(Exception):
pass
def priority(prio=0):
"""A decorator to set priority on handler functions and handlers.
Default priority is 0. Handlers with higher priority are invoked first.
Recommended priority values are 1 and -1. In most cases more than 3 priority
classes are not required.
"""
def wrap(func):
func.__priority = prio
return func
return wrap
class EventDispatcher:
"""Generic event dispatcher interface.
See the module docstring for usage.
"""
# This field will contain the queues of event handlers for every supported
# event type. It is lazily initialized when the first event handler is added
# to the class. After that it contains a dictionary of lists, in which
# handlers are sorted according to their priority:
# {'on_event': [(priority1, handler1),
# (priority2, handler2)]}
# Handlers are invoked until any one of them returns EVENT_HANDLED
_handlers = None
# Placeholder empty stack; real stack is created only if needed
_event_stack = ()
@classmethod
def register_event_type(cls, name):
"""Registers an event type with the dispatcher.
"""Register an event type with the dispatcher.
Registering event types allows the dispatcher to validate event
handler names as they are attached, and to search attached objects for
@ -295,216 +151,192 @@ class EventDispatcher:
:Parameters:
`name` : str
Name of the event to register.
"""
if not hasattr(cls, 'event_types'):
cls.event_types = []
cls.event_types.append(name)
return name
def _get_names_from_handler(self, handler):
"""Yields event names handled by a handler function, method or object.
def push_handlers(self, *args, **kwargs):
"""Push a level onto the top of the handler stack, then attach zero or
more event handlers.
If keyword arguments are given, they name the event type to attach.
Otherwise, a callable's `__name__` attribute will be used. Any other
object may also be specified, in which case it will be searched for
callables with event names.
"""
if callable(handler) and hasattr(handler, '__name__'):
# Take the name of a function or a method.
yield handler.__name__
else:
# Iterate through all the methods of an object and yield those that
# match registered events.
for name in dir(handler):
if name in self.event_types and callable(getattr(handler, name)):
yield name
# Create event stack if necessary
if type(self._event_stack) is tuple:
self._event_stack = []
def _finalize_weak_method(self, name, weak_method):
"""Called to remove dead WeakMethods from handlers."""
handlers = self._handlers[name]
i = 0
# This is not the most efficient way of removing several elements from
# an array, but in almost all cases only one element has to be removed.
while i < len(handlers):
if handlers[i][1] is weak_method:
del handlers[i]
# Place dict full of new handlers at beginning of stack
self._event_stack.insert(0, {})
self.set_handlers(*args, **kwargs)
def _get_handlers(self, args, kwargs):
"""Implement handler matching on arguments for set_handlers and
remove_handlers.
"""
for obj in args:
if inspect.isroutine(obj):
# Single magically named function
name = obj.__name__
if name not in self.event_types:
raise EventException(f'Unknown event "{name}"')
if inspect.ismethod(obj):
yield name, WeakMethod(obj, partial(self._remove_handler, name))
else:
yield name, obj
else:
i += 1
@staticmethod
def _remove_handler_from_queue(handlers_queue, handler):
"""Remove all instances of a handler from a queue for a single event.
If `handler` is an object, then all the methods bound to this object
will be removed from the queue.
"""
i = 0
# This is not the most efficient way of removing several elements from
# an array, but in almost all cases only one element has to be removed.
while i < len(handlers_queue):
_, registered_handler = handlers_queue[i]
if isinstance(registered_handler, _WeakMethod):
# Wrapped in _WeakMethod in `push_handler`.
registered_handler = registered_handler()
if registered_handler is handler or getattr(registered_handler, '__self__', None) is handler:
del handlers_queue[i]
else:
i += 1
def push_handler(self, name, handler, priority=None):
"""Adds a single event handler.
If the `handler` parameter is callable, it will be registered directly.
Otherwise, it's expected to be an object having a method with a name
matching the name of the event.
If the `priority` parameter is not None, it is used as a priotity.
Otherwise, the value specified by the @priority decorator is used. If
neither is specified the default value of 0 is used.
"""
if not hasattr(self.__class__, 'event_types'):
self.__class__.event_types = []
if name not in self.event_types:
raise EventException('Unknown event "{}"'.format(name))
if not callable(handler):
# If handler is not callable, search for in it for a method with
# a name matching the name of the event.
if hasattr(handler, name):
method = getattr(handler, name)
if not callable(method):
raise EventException(
'Field {} on "{}" is not callable'.format(
name, repr(handler)))
handler = method
else:
raise EventException(
'"{}" is not callable and doesn\'t have '
'a method "{}"'.format(repr(handler), name))
# Determine priority
if priority is None:
priority = getattr(handler, '__priority', 0)
# A hack for the case when handler is a MagicMock.
if type(priority) not in (int, float):
priority = int(priority)
# Wrap methods in weak references.
if _inspect.ismethod(handler):
handler = _WeakMethod(handler, _partial(self._finalize_weak_method, name))
# Create handler queues if necessary.
if self._handlers is None:
self._handlers = {}
self.push_handlers(self)
if name not in self._handlers:
self._handlers[name] = []
handlers = self._handlers[name]
# Finding the place to insert the new handler. All the previous handlers
# have to have strictly higher priority.
#
# A binary search would theoretically be faster, but a) there's
# usually just a handful of handlers, b) we are going to shift
# the elements in the list anyway, which will take O(n), c) we are
# doing this only during handler registration, and we are more
# conserned in the efficiency of dispatching event.
i = 0
while i < len(handlers) and handlers[i][0] > priority:
i += 1
handlers.insert(i, (priority, handler))
def push_handlers(self, *args, priority=None, **kwargs):
"""Adds new handlers to registered events.
Multiple positional and keyword arguments can be provided.
For a keyword argument, the name of the event is taken from the name
of the argument. If the argument is callable, it is used
as a handler directly. If the argument is an object, it is searched for
a method with the name matching the name of the event/argument.
When a callable named object (usually a function or a method) is passed
as a positional argument, its name is used as the event name. When
an object is passed as a positional argument, it is scanned for methods
with names that match the names of registered events. These methods are
added as handlers for the respective events.
An optional argument priority can be used to override the priority for
all the added handlers. Default priority is 0, and handlers with higher
priority will be invoked first. The priority specified in the call will
take precedence of priority, specified in @priority decorator.
EventException is raised if the event name is not registered.
"""
if not hasattr(self.__class__, 'event_types'):
self.__class__.event_types = []
for handler in args:
for name in self._get_names_from_handler(handler):
self.push_handler(name, handler, priority=priority)
# Single instance with magically named methods
for name in dir(obj):
if name in self.event_types:
meth = getattr(obj, name)
yield name, WeakMethod(meth, partial(self._remove_handler, name))
for name, handler in kwargs.items():
self.push_handler(name, handler, priority)
# Function for handling given event (no magic)
if name not in self.event_types:
raise EventException(f'Unknown event "{name}"')
if inspect.ismethod(handler):
yield name, WeakMethod(handler, partial(self._remove_handler, name))
else:
yield name, handler
def remove_handler(self, name_or_handler=None, handler=None, name=None):
"""Removes a single event handler.
def set_handlers(self, *args, **kwargs):
"""Attach one or more event handlers to the top level of the handler
stack.
Can be called in one of the following ways:
dispatcher.remove_handler(my_handler)
dispatcher.remove_handler(handler=my_handler)
dispatcher.remove_handler("event_name", my_handler)
dispatcher.remove_handler(name="event_name", handler=my_handler)
If the event name is specified, only the queue of handlers for that
event is scanned, and the handler is removed from it. Otherwise all
handler queues are scanned and the handler is removed from all of them.
If the handler is an object, then all the registered handlers that are
bound to this object are removed. Unlike `push_handler`, the method
names in the class are not taken into account.
No error is raised if the event handler is not set.
See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the accepted argument types.
"""
if handler is None:
# Called with one positional argument (example #1)
assert name is None
assert name_or_handler is not None
handler = name_or_handler
elif name is not None:
# Called with keyword arguments for handler and name (example #4)
assert name_or_handler is None
else:
# Called with two positional arguments, or only with handler as
# a keyword argument (examples #2, #3)
name = name_or_handler
# Create event stack if necessary
if type(self._event_stack) is tuple:
self._event_stack = [{}]
if name is not None:
if name in self._handlers:
self._remove_handler_from_queue(self._handlers[name], handler)
else:
for handlers_queue in self._handlers.values():
self._remove_handler_from_queue(handlers_queue, handler)
for name, handler in self._get_handlers(args, kwargs):
self.set_handler(name, handler)
def set_handler(self, name, handler):
"""Attach a single event handler.
:Parameters:
`name` : str
Name of the event type to attach to.
`handler` : callable
Event handler to attach.
"""
# Create event stack if necessary
if type(self._event_stack) is tuple:
self._event_stack = [{}]
self._event_stack[0][name] = handler
def pop_handlers(self):
"""Pop the top level of event handlers off the stack.
"""
assert self._event_stack and 'No handlers pushed'
del self._event_stack[0]
def remove_handlers(self, *args, **kwargs):
"""Removes event handlers from the event handlers queue.
"""Remove event handlers from the event stack.
See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the
accepted argument types. Handlers, passed as positional arguments
are removed from all events, regardless of their names.
accepted argument types. All handlers are removed from the first stack
frame that contains any of the given handlers. No error is raised if
any handler does not appear in that frame, or if no stack frame
contains any of the given handlers.
No error is raised if any handler does not appear among
the registered handlers.
If the stack frame is empty after removing the handlers, it is
removed from the stack. Note that this interferes with the expected
symmetry of :py:meth:`~pyglet.event.EventDispatcher.push_handlers` and
:py:meth:`~pyglet.event.EventDispatcher.pop_handlers`.
"""
for handler in args:
self.remove_handler(None, handler)
handlers = list(self._get_handlers(args, kwargs))
for name, handler in kwargs.items():
self.remove_handler(name, handler)
# Find the first stack frame containing any of the handlers
def find_frame():
for frame in self._event_stack:
for name, handler in handlers:
try:
if frame[name] == handler:
return frame
except KeyError:
pass
frame = find_frame()
# No frame matched; no error.
if not frame:
return
# Remove each handler from the frame.
for name, handler in handlers:
try:
if frame[name] == handler:
del frame[name]
except KeyError:
pass
# Remove the frame if it's empty.
if not frame:
self._event_stack.remove(frame)
def remove_handler(self, name, handler):
"""Remove a single event handler.
The given event handler is removed from the first handler stack frame
it appears in. The handler must be the exact same callable as passed
to `set_handler`, `set_handlers` or
:py:meth:`~pyglet.event.EventDispatcher.push_handlers`; and the name
must match the event type it is bound to.
No error is raised if the event handler is not set.
:Parameters:
`name` : str
Name of the event type to remove.
`handler` : callable
Event handler to remove.
"""
for frame in self._event_stack:
try:
if frame[name] == handler:
del frame[name]
break
except KeyError:
pass
def _remove_handler(self, name, handler):
"""Used internally to remove all handler instances for the given event name.
This is normally called from a dead ``WeakMethod`` to remove itself from the
event stack.
"""
# Iterate over a copy as we might mutate the list
for frame in list(self._event_stack):
if name in frame:
try:
if frame[name] == handler:
del frame[name]
if not frame:
self._event_stack.remove(frame)
except TypeError:
# weakref is already dead
pass
def dispatch_event(self, event_type, *args):
"""Dispatch a single event to the attached handlers.
The event is propagated to all handlers from the top of the stack
The event is propagated to all handlers from from the top of the stack
until one returns `EVENT_HANDLED`. This method should be used only by
:py:class:`~pyglet.event.EventDispatcher` implementors; applications
should call the ``dispatch_events`` method.
:py:class:`~pyglet.event.EventDispatcher` implementors; applications should call
the ``dispatch_events`` method.
Since pyglet 1.2, the method returns `EVENT_HANDLED` if an event
handler returned `EVENT_HANDLED` or `EVENT_UNHANDLED` if all events
@ -525,32 +357,48 @@ class EventDispatcher:
is always ``None``.
"""
if not hasattr(self.__class__, 'event_types'):
self.__class__.event_types = []
if event_type not in self.event_types:
raise EventException(
'Attempted to dispatch an event of unknown event type "{}". '
'Event types have to be registered by calling '
'DispatcherClass.register_event_type({})'.format(
event_type, repr(event_type)))
assert hasattr(self, 'event_types'), (
"No events registered on this EventDispatcher. "
"You need to register events with the class method "
"EventDispatcher.register_event_type('event_name')."
)
assert event_type in self.event_types, \
f"{event_type} not found in {self}.event_types == {self.event_types}"
if self._handlers is None:
# Initialize the handlers with the object itself.
self._handlers = {}
self.push_handlers(self)
invoked = False
handlers_queue = self._handlers.get(event_type, ())
for _, handler in handlers_queue:
if isinstance(handler, _WeakMethod):
# Search handler stack for matching event handlers
for frame in list(self._event_stack):
handler = frame.get(event_type, None)
if not handler:
continue
if isinstance(handler, WeakMethod):
handler = handler()
assert handler is not None
try:
invoked = True
if handler(*args):
return EVENT_HANDLED
except TypeError as exception:
self._raise_dispatch_exception(event_type, args, handler, exception)
return EVENT_UNHANDLED
# Check instance for an event handler
try:
if getattr(self, event_type)(*args):
return EVENT_HANDLED
except AttributeError as e:
event_op = getattr(self, event_type, None)
if callable(event_op):
raise e
except TypeError as exception:
self._raise_dispatch_exception(event_type, args, getattr(self, event_type), exception)
else:
invoked = True
if invoked:
return EVENT_UNHANDLED
return False
@staticmethod
def _raise_dispatch_exception(event_type, args, handler, exception):
@ -566,7 +414,7 @@ class EventDispatcher:
n_args = len(args)
# Inspect the handler
argspecs = _inspect.getfullargspec(handler)
argspecs = inspect.getfullargspec(handler)
handler_args = argspecs.args
handler_varargs = argspecs.varargs
handler_defaults = argspecs.defaults
@ -574,7 +422,7 @@ class EventDispatcher:
n_handler_args = len(handler_args)
# Remove "self" arg from handler if it's a bound method
if _inspect.ismethod(handler) and handler.__self__:
if inspect.ismethod(handler) and handler.__self__:
n_handler_args -= 1
# Allow *args varargs to overspecify arguments
@ -586,15 +434,14 @@ class EventDispatcher:
n_handler_args = n_args
if n_handler_args != n_args:
if _inspect.isfunction(handler) or _inspect.ismethod(handler):
descr = "'%s' at %s:%d" % (handler.__name__,
handler.__code__.co_filename,
handler.__code__.co_firstlineno)
if inspect.isfunction(handler) or inspect.ismethod(handler):
descr = f"'{handler.__name__}' at {handler.__code__.co_filename}:{handler.__code__.co_firstlineno}"
else:
descr = repr(handler)
raise TypeError(f"The '{event_type}' event was dispatched with {len(args)} arguments, "
f"but your handler {descr} accepts only {len(handler_args)} arguments.")
raise TypeError(f"The '{event_type}' event was dispatched with {len(args)} arguments,\n"
f"but your handler {descr} accepts only {n_handler_args} arguments.")
else:
raise exception
@ -616,22 +463,23 @@ class EventDispatcher:
# ...
"""
if len(args) == 0: # @window.event()
if len(args) == 0: # @window.event()
def decorator(func):
name = func.__name__
self.push_handler(name, func)
func_name = func.__name__
self.set_handler(func_name, func)
return func
return decorator
elif _inspect.isroutine(args[0]): # @window.event
return decorator
elif inspect.isroutine(args[0]): # @window.event
func = args[0]
name = func.__name__
self.push_handler(name, func)
self.set_handler(name, func)
return args[0]
elif isinstance(args[0], str): # @window.event('on_resize')
elif isinstance(args[0], str): # @window.event('on_resize')
name = args[0]
def decorator(func):
self.push_handler(name, func)
self.set_handler(name, func)
return func
return decorator

View File

@ -1426,12 +1426,6 @@ class Texture(AbstractImage):
order = self.tex_coords_order
self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
@property
def uv(self):
"""Tuple containing the left, bottom, right, top 2D texture coordinates."""
tex_coords = self.tex_coords
return tex_coords[0], tex_coords[1], tex_coords[3], tex_coords[7]
def __repr__(self):
return "{}(id={}, size={}x{})".format(self.__class__.__name__, self.id, self.width, self.height)

View File

@ -78,32 +78,16 @@ from pyglet import image
_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
vertex_source = """#version 150
vertex_source = """#version 150 core
in vec3 translate;
in vec4 colors;
in vec3 tex_coords;
in vec2 scale;
in vec3 position;
in vec4 size;
in vec4 color;
in vec4 texture_uv;
in float rotation;
out vec4 geo_size;
out vec4 geo_color;
out vec4 geo_tex_coords;
out float geo_rotation;
void main() {
gl_Position = vec4(position, 1);
geo_size = size;
geo_color = color;
geo_tex_coords = texture_uv;
geo_rotation = rotation;
}
"""
geometry_source = """#version 150
// We are taking single points form the vertex shader
// and emitting 4 new vertices creating a quad/sprites
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
out vec4 vertex_colors;
out vec3 texture_coords;
uniform WindowBlock
{
@ -111,102 +95,52 @@ geometry_source = """#version 150
mat4 view;
} window;
mat4 m_scale = mat4(1.0);
mat4 m_rotation = mat4(1.0);
mat4 m_translate = mat4(1.0);
// Since geometry shader can take multiple values from a vertex
// shader we need to define the inputs from it as arrays.
// In our instance we just take single values (points)
in vec4 geo_size[];
in vec4 geo_color[];
in vec4 geo_tex_coords[];
in float geo_rotation[];
void main()
{
m_scale[0][0] = scale.x;
m_scale[1][1] = scale.y;
m_translate[3][0] = translate.x;
m_translate[3][1] = translate.y;
m_translate[3][2] = translate.z;
m_rotation[0][0] = cos(-radians(rotation));
m_rotation[0][1] = sin(-radians(rotation));
m_rotation[1][0] = -sin(-radians(rotation));
m_rotation[1][1] = cos(-radians(rotation));
out vec2 uv;
out vec4 frag_color;
gl_Position = window.projection * window.view * m_translate * m_rotation * m_scale * vec4(position, 1.0);
void main() {
// We grab the position value from the vertex shader
vec2 center = gl_in[0].gl_Position.xy;
// Calculate the half size of the sprites for easier calculations
vec2 hsize = geo_size[0].xy / 2.0;
// Convert the rotation to radians
float angle = radians(-geo_rotation[0]);
// Create a scale vector
vec2 scale = vec2(geo_size[0][2], geo_size[0][3]);
// Create a 2d rotation matrix
mat2 rot = mat2(cos(angle), sin(angle),
-sin(angle), cos(angle));
// Calculate the left, bottom, right, top:
float tl = geo_tex_coords[0].s;
float tb = geo_tex_coords[0].t;
float tr = geo_tex_coords[0].s + geo_tex_coords[0].p;
float tt = geo_tex_coords[0].t + geo_tex_coords[0].q;
// Emit a triangle strip creating a quad (4 vertices).
// Here we need to make sure the rotation is applied before we position the sprite.
// We just use hardcoded texture coordinates here. If an atlas is used we
// can pass an additional vec4 for specific texture coordinates.
// Each EmitVertex() emits values down the shader pipeline just like a single
// run of a vertex shader, but in geomtry shaders we can do it multiple times!
// Upper left
gl_Position = window.projection * window.view * vec4(rot * vec2(-hsize.x, hsize.y) * scale + center, 0.0, 1.0);
uv = vec2(tl, tt);
frag_color = geo_color[0];
EmitVertex();
// lower left
gl_Position = window.projection * window.view * vec4(rot * vec2(-hsize.x, -hsize.y) * scale + center, 0.0, 1.0);
uv = vec2(tl, tb);
frag_color = geo_color[0];
EmitVertex();
// upper right
gl_Position = window.projection * window.view * vec4(rot * vec2(hsize.x, hsize.y) * scale + center, 0.0, 1.0);
uv = vec2(tr, tt);
frag_color = geo_color[0];
EmitVertex();
// lower right
gl_Position = window.projection * window.view * vec4(rot * vec2(hsize.x, -hsize.y) * scale + center, 0.0, 1.0);
uv = vec2(tr, tb);
frag_color = geo_color[0];
EmitVertex();
// We are done with this triangle strip now
EndPrimitive();
vertex_colors = colors;
texture_coords = tex_coords;
}
"""
fragment_source = """#version 150
in vec2 uv;
in vec4 frag_color;
out vec4 final_color;
fragment_source = """#version 150 core
in vec4 vertex_colors;
in vec3 texture_coords;
out vec4 final_colors;
uniform sampler2D sprite_texture;
void main() {
final_color = texture(sprite_texture, uv) * frag_color;
void main()
{
final_colors = texture(sprite_texture, texture_coords.xy) * vertex_colors;
}
"""
fragment_array_source = """#version 150 core
in vec2 uv;
in vec4 frag_color;
in vec4 vertex_colors;
in vec3 texture_coords;
out vec4 final_colors;
uniform sampler2DArray sprite_texture;
void main()
{
final_colors = texture(sprite_texture, uv) * frag_color;
final_colors = texture(sprite_texture, texture_coords) * vertex_colors;
}
"""
@ -215,10 +149,9 @@ def get_default_shader():
try:
return pyglet.gl.current_context.pyglet_sprite_default_shader
except AttributeError:
vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
geom_shader = graphics.shader.Shader(geometry_source, 'geometry')
frag_shader = graphics.shader.Shader(fragment_source, 'fragment')
default_shader_program = graphics.shader.ShaderProgram(vert_shader, geom_shader, frag_shader)
_default_vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
_default_frag_shader = graphics.shader.Shader(fragment_source, 'fragment')
default_shader_program = graphics.shader.ShaderProgram(_default_vert_shader, _default_frag_shader)
pyglet.gl.current_context.pyglet_sprite_default_shader = default_shader_program
return pyglet.gl.current_context.pyglet_sprite_default_shader
@ -227,10 +160,9 @@ def get_default_array_shader():
try:
return pyglet.gl.current_context.pyglet_sprite_default_array_shader
except AttributeError:
vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
geom_shader = graphics.shader.Shader(geometry_source, 'geometry')
frag_shader = graphics.shader.Shader(fragment_array_source, 'fragment')
default_shader_program = graphics.shader.ShaderProgram(vert_shader, geom_shader, frag_shader)
_default_vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
_default_array_frag_shader = graphics.shader.Shader(fragment_array_source, 'fragment')
default_shader_program = graphics.shader.ShaderProgram(_default_vert_shader, _default_array_frag_shader)
pyglet.gl.current_context.pyglet_sprite_default_array_shader = default_shader_program
return pyglet.gl.current_context.pyglet_sprite_default_array_shader
@ -312,7 +244,8 @@ class Sprite(event.EventDispatcher):
_frame_index = 0
_paused = False
_rotation = 0
_rgba = [255, 255, 255, 255]
_opacity = 255
_rgb = (255, 255, 255)
_scale = 1.0
_scale_x = 1.0
_scale_y = 1.0
@ -326,8 +259,7 @@ class Sprite(event.EventDispatcher):
blend_dest=GL_ONE_MINUS_SRC_ALPHA,
batch=None,
group=None,
subpixel=False,
program=None):
subpixel=False):
"""Create a sprite.
:Parameters:
@ -352,11 +284,6 @@ class Sprite(event.EventDispatcher):
`subpixel` : bool
Allow floating-point coordinates for the sprite. By default,
coordinates are restricted to integer values.
`program` : `~pyglet.graphics.shader.ShaderProgram`
A custom shader to use. This shader program must contain the
exact same attribute names and types as the default shader.
The class methods and properties depend on this, and will
crash otherwise.
"""
self._x = x
self._y = y
@ -372,46 +299,26 @@ class Sprite(event.EventDispatcher):
else:
self._texture = img.get_texture()
if not program:
if isinstance(img, image.TextureArrayRegion):
self._program = get_default_array_shader()
else:
self._program = get_default_shader()
else:
self._program = program
self._batch = batch or graphics.get_default_batch()
self._user_group = group
self._group = self.group_class(self._texture, blend_src, blend_dest, self.program, group)
self._subpixel = subpixel
self._create_vertex_list()
def _create_vertex_list(self):
texture = self._texture
self._vertex_list = self.program.vertex_list(
1, GL_POINTS, self._batch, self._group,
position=('f', (self._x, self._y, self._z)),
size=('f', (texture.width, texture.height, 1, 1)),
color=('Bn', self._rgba),
texture_uv=('f', texture.uv),
rotation=('f', (self._rotation,)))
@property
def program(self):
return self._program
if isinstance(self._img, image.TextureArrayRegion):
program = get_default_array_shader()
else:
program = get_default_shader()
@program.setter
def program(self, program):
if self._program == program:
return
self._group = self.group_class(self._texture,
self._group.blend_src,
self._group.blend_dest,
program,
self._user_group)
self._batch.migrate(self._vertex_list, GL_POINTS, self._group, self._batch)
self._program = program
return program
def __del__(self):
try:
if self._vertex_list is not None:
self._vertex_list.delete()
except:
pass
def delete(self):
"""Force immediate removal of the sprite from video memory.
@ -425,6 +332,8 @@ class Sprite(event.EventDispatcher):
self._vertex_list.delete()
self._vertex_list = None
self._texture = None
# Easy way to break circular reference, speeds up GC
self._group = None
def _animate(self, dt):
@ -464,7 +373,7 @@ class Sprite(event.EventDispatcher):
return
if batch is not None and self._batch is not None:
self._batch.migrate(self._vertex_list, GL_POINTS, self._group, batch)
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, batch)
self._batch = batch
else:
self._vertex_list.delete()
@ -491,7 +400,7 @@ class Sprite(event.EventDispatcher):
self._group.blend_dest,
self._group.program,
group)
self._batch.migrate(self._vertex_list, GL_POINTS, self._group, self._batch)
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, self._batch)
@property
def image(self):
@ -519,6 +428,7 @@ class Sprite(event.EventDispatcher):
clock.schedule_once(self._animate, self._next_dt)
else:
self._set_texture(img.get_texture())
self._update_position()
def _set_texture(self, texture):
if texture.id is not self._texture.id:
@ -531,9 +441,35 @@ class Sprite(event.EventDispatcher):
self._texture = texture
self._create_vertex_list()
else:
self._vertex_list.texture_uv[:] = texture.uv
self._vertex_list.tex_coords[:] = texture.tex_coords
self._texture = texture
def _create_vertex_list(self):
self._vertex_list = self.program.vertex_list_indexed(
4, GL_TRIANGLES, [0, 1, 2, 0, 2, 3], self._batch, self._group,
colors=('Bn', (*self._rgb, int(self._opacity)) * 4),
translate=('f', (self._x, self._y, self._z) * 4),
scale=('f', (self._scale*self._scale_x, self._scale*self._scale_y) * 4),
rotation=('f', (self._rotation,) * 4),
tex_coords=('f', self._texture.tex_coords))
self._update_position()
def _update_position(self):
if not self._visible:
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
else:
img = self._texture
x1 = -img.anchor_x
y1 = -img.anchor_y
x2 = x1 + img.width
y2 = y1 + img.height
vertices = (x1, y1, 0, x2, y1, 0, x2, y2, 0, x1, y2, 0)
if not self._subpixel:
self._vertex_list.position[:] = tuple(map(int, vertices))
else:
self._vertex_list.position[:] = vertices
@property
def position(self):
"""The (x, y, z) coordinates of the sprite, as a tuple.
@ -551,7 +487,7 @@ class Sprite(event.EventDispatcher):
@position.setter
def position(self, position):
self._x, self._y, self._z = position
self._vertex_list.position[:] = position
self._vertex_list.translate[:] = position * 4
@property
def x(self):
@ -564,7 +500,7 @@ class Sprite(event.EventDispatcher):
@x.setter
def x(self, x):
self._x = x
self._vertex_list.position[:] = x, self._y, self._z
self._vertex_list.translate[:] = (x, self._y, self._z) * 4
@property
def y(self):
@ -577,7 +513,7 @@ class Sprite(event.EventDispatcher):
@y.setter
def y(self, y):
self._y = y
self._vertex_list.position[:] = self._x, y, self._z
self._vertex_list.translate[:] = (self._x, y, self._z) * 4
@property
def z(self):
@ -590,7 +526,7 @@ class Sprite(event.EventDispatcher):
@z.setter
def z(self, z):
self._z = z
self._vertex_list.position[:] = self._x, self._y, z
self._vertex_list.translate[:] = (self._x, self._y, z) * 4
@property
def rotation(self):
@ -606,7 +542,7 @@ class Sprite(event.EventDispatcher):
@rotation.setter
def rotation(self, rotation):
self._rotation = rotation
self._vertex_list.rotation[0] = self._rotation
self._vertex_list.rotation[:] = (self._rotation,) * 4
@property
def scale(self):
@ -622,7 +558,7 @@ class Sprite(event.EventDispatcher):
@scale.setter
def scale(self, scale):
self._scale = scale
self._vertex_list.scale[:] = scale * self._scale_x, scale * self._scale_y
self._vertex_list.scale[:] = (scale * self._scale_x, scale * self._scale_y) * 4
@property
def scale_x(self):
@ -638,7 +574,7 @@ class Sprite(event.EventDispatcher):
@scale_x.setter
def scale_x(self, scale_x):
self._scale_x = scale_x
self._vertex_list.scale[:] = self._scale * scale_x, self._scale * self._scale_y
self._vertex_list.scale[:] = (self._scale * scale_x, self._scale * self._scale_y) * 4
@property
def scale_y(self):
@ -654,7 +590,7 @@ class Sprite(event.EventDispatcher):
@scale_y.setter
def scale_y(self, scale_y):
self._scale_y = scale_y
self._vertex_list.scale[:] = self._scale * self._scale_x, self._scale * scale_y
self._vertex_list.scale[:] = (self._scale * self._scale_x, self._scale * scale_y) * 4
def update(self, x=None, y=None, z=None, rotation=None, scale=None, scale_x=None, scale_y=None):
"""Simultaneously change the position, rotation or scale.
@ -693,11 +629,11 @@ class Sprite(event.EventDispatcher):
translations_outdated = True
if translations_outdated:
self._vertex_list.position[:] = (self._x, self._y, self._z)
self._vertex_list.translate[:] = (self._x, self._y, self._z) * 4
if rotation is not None and rotation != self._rotation:
self._rotation = rotation
self._vertex_list.rotation[:] = rotation
self._vertex_list.rotation[:] = (rotation,) * 4
scales_outdated = False
@ -713,7 +649,7 @@ class Sprite(event.EventDispatcher):
scales_outdated = True
if scales_outdated:
self._vertex_list.scale[:] = self._scale * self._scale_x, self._scale * self._scale_y
self._vertex_list.scale[:] = (self._scale * self._scale_x, self._scale * self._scale_y) * 4
@property
def width(self):
@ -755,12 +691,12 @@ class Sprite(event.EventDispatcher):
:type: int
"""
return self._rgba[3]
return self._opacity
@opacity.setter
def opacity(self, opacity):
self._rgba[3] = opacity
self._vertex_list.color[:] = self._rgba
self._opacity = opacity
self._vertex_list.colors[:] = (*self._rgb, int(self._opacity)) * 4
@property
def color(self):
@ -774,12 +710,12 @@ class Sprite(event.EventDispatcher):
:type: (int, int, int)
"""
return self._rgba[:3]
return self._rgb
@color.setter
def color(self, rgb):
self._rgba[:3] = list(map(int, rgb))
self._vertex_list.color[:] = self._rgba
self._rgb = list(map(int, rgb))
self._vertex_list.colors[:] = (*self._rgb, int(self._opacity)) * 4
@property
def visible(self):
@ -792,7 +728,7 @@ class Sprite(event.EventDispatcher):
@visible.setter
def visible(self, visible):
self._visible = visible
self._vertex_list.texture_uv[:] = (0, 0, 0, 0) if not visible else self._texture.uv
self._update_position()
@property
def paused(self):
@ -846,16 +782,9 @@ class Sprite(event.EventDispatcher):
efficiently.
"""
self._group.set_state_recursive()
self._vertex_list.draw(GL_POINTS)
self._vertex_list.draw(GL_TRIANGLES)
self._group.unset_state_recursive()
def __del__(self):
try:
if self._vertex_list is not None:
self._vertex_list.delete()
except:
pass
if _is_pyglet_doc_run:
def on_animation_end(self):
"""The sprite animation reached the final frame.
@ -869,3 +798,46 @@ class Sprite(event.EventDispatcher):
Sprite.register_event_type('on_animation_end')
class AdvancedSprite(pyglet.sprite.Sprite):
"""Is a sprite that lets you change the shader program during initialization and after
For advanced users who understand shaders."""
def __init__(self,
img, x=0, y=0, z=0,
blend_src=GL_SRC_ALPHA,
blend_dest=GL_ONE_MINUS_SRC_ALPHA,
batch=None,
group=None,
subpixel=False,
program=None):
self._program = program
if not program:
if isinstance(img, image.TextureArrayRegion):
self._program = get_default_array_shader()
else:
self._program = get_default_shader()
super().__init__(img, x, y, z, blend_src, blend_dest, batch, group, subpixel)
@property
def program(self):
return self._program
@program.setter
def program(self, program):
if self._program == program:
return
self._group = self.group_class(self._texture,
self._group.blend_src,
self._group.blend_dest,
program,
self._group)
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, self._batch)
self._program = program

View File

@ -9,6 +9,7 @@
pub mod python_class {
use pyo3::class::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::PySlice;
use crate::math::matrix::{Matrix3, Matrix4};
use crate::math::vector::{Vector2, Vector3, Vector4, VectorTrait};
@ -131,6 +132,13 @@ pub mod python_class {
return format!("Vector2_rs({}, {})", self.data.x, self.data.y);
}
// fn __getitem__(&self, item: &PyAny) -> PyResult<&PyAny> {
// if item.is_instance_of::<PySlice>().unwrap() {
// let item = item.extract::<PySlice>().unwrap();
// let indices = item.indices().unwrap();
// }
// }
// getter and setter
#[getter]