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, self.input_box = InputBox(x=50, y=30, width=300,
batch=self.label_batch, text='') # 实例化 batch=self.label_batch, text='') # 实例化
self.input_box.push_handlers(self) self.input_box.push_handlers(self)
self.input_box.push_handler('on_commit', self.on_input) self.input_box.set_handler('on_commit', self.on_input)
self.push_handlers(self.input_box) self.set_handlers(self.input_box)
self.input_box.enabled = True self.input_box.enabled = True
# 设置刷新率 # 设置刷新率
pyglet.clock.schedule_interval(self.draw_update, float(self.SPF)) 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. """Event dispatch framework.
All objects that produce events in pyglet implement :py:class:`~pyglet.event.EventDispatcher`, 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 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 correspond with the type of event handlers you can attach. Event types are
identified by their name, for example, ''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
If you are creating a new class which implements `EventDispatcher.register_event_type` for each event type.
: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')
Attaching event handlers Attaching event handlers
======================== ========================
An event handler is simply a function or method, that is called when system or An event handler is simply a function or method. You can attach an event
program event happens. There are several ways to add a handler for an event. handler by setting the appropriate function on the instance::
When the dispatcher object is available as a global variable, it is convenient def on_resize(width, height):
to use the `event` decorator: # ...
dispatcher.on_resize = on_resize
@window.event There is also a convenience decorator that reduces typing::
@dispatcher.event
def on_resize(width, height): def on_resize(width, height):
# ... # ...
Here `window` is a variable containing an instance of `pyglet.window.Window`, You may prefer to subclass and override the event handlers instead::
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:
@window.event('on_resize') class MyDispatcher(DispatcherClass):
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):
def on_resize(self, width, height): def on_resize(self, width, height):
# ... # ...
If both a parent class and the child class have a handler for the same event, Event handler stack
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:
class ParentDispatcher(pyglet.event.EventDispatcher): When attaching an event handler to a dispatcher using the above methods, it
def on_resize(self, w, h); 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): There are two main use cases for "pushing" event handlers:
def on_resize(self, w, h):
super().on_resize(w, h)
# ...
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 Use `EventDispatcher.push_handlers` to create a new level in the stack and
the order opposite to the order of their registration. So, the handler attach handlers to it. You can push several handlers at once::
registered last will be the first to be invoke when the event is fired.
An event handler can return the value `pyglet.event.EVENT_HANDLED` to prevent dispatcher.push_handlers(on_resize, on_key_press)
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).
Stopping the event propagation is useful to prevent a single user action from If your function handlers have different names to the events they handle, use
being handled by two unrelated systems. For instance, in game using WASD keys keyword arguments::
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.
The order of execution of event handlers can be changed by assigning them dispatcher.push_handlers(on_resize=my_resize, on_key_press=my_key_press)
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.
Priority can be assigned by passing the `priority` named parameter to After an event handler has processed an event, it is passed on to the
`push_handlers` method: 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 Note that any handlers pushed onto the stack have precedence over the
functions and methods: 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) dispatcher.push_handlers(on_resize=foo)
def on_resize(w, h): dispatcher.on_resize = bar
# ...
class Listener(object): Dispatching events
@pyglet.event.priority(-1) ==================
def on_resize(self, w, h):
# ...
listener = Listener()
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 This implies that your application runs with a main loop that continuously
the handler is an object method, the event dispatcher keeps only a weak updates the application state and checks for new events::
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 while True:
silently removed from the list of handlers. dispatcher.dispatch_events()
# ... additional per-frame processing
Not all event dispatchers require the call to ``dispatch_events``; check with
the particular class documentation.
.. note:: .. note::
This means the following example will not work, because the pushed object In order to prevent issues with garbage collection, the
will fall out of scope and be collected:: :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()) dispatcher.push_handlers(MyHandlerClass())
@ -202,50 +115,12 @@ silently removed from the list of handlers.
my_handler_instance = MyHandlerClass() my_handler_instance = MyHandlerClass()
dispatcher.push_handlers(my_handler_instance) 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 import inspect
from functools import partial as _partial
from weakref import WeakMethod as _WeakMethod
from functools import partial
from weakref import WeakMethod
EVENT_HANDLED = True EVENT_HANDLED = True
EVENT_UNHANDLED = None EVENT_UNHANDLED = None
@ -257,36 +132,17 @@ class EventException(Exception):
pass 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: class EventDispatcher:
"""Generic event dispatcher interface. """Generic event dispatcher interface.
See the module docstring for usage. See the module docstring for usage.
""" """
# This field will contain the queues of event handlers for every supported # Placeholder empty stack; real stack is created only if needed
# event type. It is lazily initialized when the first event handler is added _event_stack = ()
# 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
@classmethod @classmethod
def register_event_type(cls, name): 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 Registering event types allows the dispatcher to validate event
handler names as they are attached, and to search attached objects for handler names as they are attached, and to search attached objects for
@ -295,216 +151,192 @@ class EventDispatcher:
:Parameters: :Parameters:
`name` : str `name` : str
Name of the event to register. Name of the event to register.
""" """
if not hasattr(cls, 'event_types'): if not hasattr(cls, 'event_types'):
cls.event_types = [] cls.event_types = []
cls.event_types.append(name) cls.event_types.append(name)
return name
def _get_names_from_handler(self, handler): def push_handlers(self, *args, **kwargs):
"""Yields event names handled by a handler function, method or object. """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__'): # Create event stack if necessary
# Take the name of a function or a method. if type(self._event_stack) is tuple:
yield handler.__name__ self._event_stack = []
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
def _finalize_weak_method(self, name, weak_method): # Place dict full of new handlers at beginning of stack
"""Called to remove dead WeakMethods from handlers.""" self._event_stack.insert(0, {})
handlers = self._handlers[name] self.set_handlers(*args, **kwargs)
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]
else:
i += 1
@staticmethod def _get_handlers(self, args, kwargs):
def _remove_handler_from_queue(handlers_queue, handler): """Implement handler matching on arguments for set_handlers and
"""Remove all instances of a handler from a queue for a single event. remove_handlers.
If `handler` is an object, then all the methods bound to this object
will be removed from the queue.
""" """
i = 0 for obj in args:
# This is not the most efficient way of removing several elements from if inspect.isroutine(obj):
# an array, but in almost all cases only one element has to be removed. # Single magically named function
while i < len(handlers_queue): name = obj.__name__
_, 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: if name not in self.event_types:
raise EventException('Unknown event "{}"'.format(name)) raise EventException(f'Unknown event "{name}"')
if not callable(handler): if inspect.ismethod(obj):
# If handler is not callable, search for in it for a method with yield name, WeakMethod(obj, partial(self._remove_handler, name))
# 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: else:
raise EventException( yield name, obj
'"{}" is not callable and doesn\'t have ' else:
'a method "{}"'.format(repr(handler), name)) # Single instance with magically named methods
for name in dir(obj):
# Determine priority if name in self.event_types:
if priority is None: meth = getattr(obj, name)
priority = getattr(handler, '__priority', 0) yield name, WeakMethod(meth, partial(self._remove_handler, name))
# 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)
for name, handler in kwargs.items(): 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): def set_handlers(self, *args, **kwargs):
"""Removes a single event handler. """Attach one or more event handlers to the top level of the handler
stack.
Can be called in one of the following ways: See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the accepted argument types.
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.
""" """
if handler is None: # Create event stack if necessary
# Called with one positional argument (example #1) if type(self._event_stack) is tuple:
assert name is None self._event_stack = [{}]
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
if name is not None: for name, handler in self._get_handlers(args, kwargs):
if name in self._handlers: self.set_handler(name, handler)
self._remove_handler_from_queue(self._handlers[name], handler)
else: def set_handler(self, name, handler):
for handlers_queue in self._handlers.values(): """Attach a single event handler.
self._remove_handler_from_queue(handlers_queue, 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): 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 See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the
accepted argument types. Handlers, passed as positional arguments accepted argument types. All handlers are removed from the first stack
are removed from all events, regardless of their names. 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 If the stack frame is empty after removing the handlers, it is
the registered handlers. 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: handlers = list(self._get_handlers(args, kwargs))
self.remove_handler(None, handler)
for name, handler in kwargs.items(): # Find the first stack frame containing any of the handlers
self.remove_handler(name, handler) 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): def dispatch_event(self, event_type, *args):
"""Dispatch a single event to the attached handlers. """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 until one returns `EVENT_HANDLED`. This method should be used only by
:py:class:`~pyglet.event.EventDispatcher` implementors; applications :py:class:`~pyglet.event.EventDispatcher` implementors; applications should call
should call the ``dispatch_events`` method. the ``dispatch_events`` method.
Since pyglet 1.2, the method returns `EVENT_HANDLED` if an event Since pyglet 1.2, the method returns `EVENT_HANDLED` if an event
handler returned `EVENT_HANDLED` or `EVENT_UNHANDLED` if all events handler returned `EVENT_HANDLED` or `EVENT_UNHANDLED` if all events
@ -525,33 +357,49 @@ class EventDispatcher:
is always ``None``. is always ``None``.
""" """
if not hasattr(self.__class__, 'event_types'): assert hasattr(self, 'event_types'), (
self.__class__.event_types = [] "No events registered on this EventDispatcher. "
if event_type not in self.event_types: "You need to register events with the class method "
raise EventException( "EventDispatcher.register_event_type('event_name')."
'Attempted to dispatch an event of unknown event type "{}". ' )
'Event types have to be registered by calling ' assert event_type in self.event_types, \
'DispatcherClass.register_event_type({})'.format( f"{event_type} not found in {self}.event_types == {self.event_types}"
event_type, repr(event_type)))
if self._handlers is None: invoked = False
# Initialize the handlers with the object itself.
self._handlers = {}
self.push_handlers(self)
handlers_queue = self._handlers.get(event_type, ()) # Search handler stack for matching event handlers
for _, handler in handlers_queue: for frame in list(self._event_stack):
if isinstance(handler, _WeakMethod): handler = frame.get(event_type, None)
if not handler:
continue
if isinstance(handler, WeakMethod):
handler = handler() handler = handler()
assert handler is not None assert handler is not None
try: try:
invoked = True
if handler(*args): if handler(*args):
return EVENT_HANDLED return EVENT_HANDLED
except TypeError as exception: except TypeError as exception:
self._raise_dispatch_exception(event_type, args, handler, exception) self._raise_dispatch_exception(event_type, args, handler, exception)
# 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 EVENT_UNHANDLED
return False
@staticmethod @staticmethod
def _raise_dispatch_exception(event_type, args, handler, exception): def _raise_dispatch_exception(event_type, args, handler, exception):
# A common problem in applications is having the wrong number of # A common problem in applications is having the wrong number of
@ -566,7 +414,7 @@ class EventDispatcher:
n_args = len(args) n_args = len(args)
# Inspect the handler # Inspect the handler
argspecs = _inspect.getfullargspec(handler) argspecs = inspect.getfullargspec(handler)
handler_args = argspecs.args handler_args = argspecs.args
handler_varargs = argspecs.varargs handler_varargs = argspecs.varargs
handler_defaults = argspecs.defaults handler_defaults = argspecs.defaults
@ -574,7 +422,7 @@ class EventDispatcher:
n_handler_args = len(handler_args) n_handler_args = len(handler_args)
# Remove "self" arg from handler if it's a bound method # 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 n_handler_args -= 1
# Allow *args varargs to overspecify arguments # Allow *args varargs to overspecify arguments
@ -586,15 +434,14 @@ class EventDispatcher:
n_handler_args = n_args n_handler_args = n_args
if n_handler_args != n_args: if n_handler_args != n_args:
if _inspect.isfunction(handler) or _inspect.ismethod(handler): if inspect.isfunction(handler) or inspect.ismethod(handler):
descr = "'%s' at %s:%d" % (handler.__name__, descr = f"'{handler.__name__}' at {handler.__code__.co_filename}:{handler.__code__.co_firstlineno}"
handler.__code__.co_filename,
handler.__code__.co_firstlineno)
else: else:
descr = repr(handler) descr = repr(handler)
raise TypeError(f"The '{event_type}' event was dispatched with {len(args)} arguments, " raise TypeError(f"The '{event_type}' event was dispatched with {len(args)} arguments,\n"
f"but your handler {descr} accepts only {len(handler_args)} arguments.") f"but your handler {descr} accepts only {n_handler_args} arguments.")
else: else:
raise exception raise exception
@ -618,20 +465,21 @@ class EventDispatcher:
""" """
if len(args) == 0: # @window.event() if len(args) == 0: # @window.event()
def decorator(func): def decorator(func):
name = func.__name__ func_name = func.__name__
self.push_handler(name, func) self.set_handler(func_name, func)
return func return func
return decorator
elif _inspect.isroutine(args[0]): # @window.event return decorator
elif inspect.isroutine(args[0]): # @window.event
func = args[0] func = args[0]
name = func.__name__ name = func.__name__
self.push_handler(name, func) self.set_handler(name, func)
return args[0] return args[0]
elif isinstance(args[0], str): # @window.event('on_resize') elif isinstance(args[0], str): # @window.event('on_resize')
name = args[0] name = args[0]
def decorator(func): def decorator(func):
self.push_handler(name, func) self.set_handler(name, func)
return func return func
return decorator return decorator

View File

@ -1426,12 +1426,6 @@ class Texture(AbstractImage):
order = self.tex_coords_order order = self.tex_coords_order
self.tex_coords_order = (order[bl], order[br], order[tr], order[tl]) 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): def __repr__(self):
return "{}(id={}, size={}x{})".format(self.__class__.__name__, self.id, self.width, self.height) 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 _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 vec3 position;
in vec4 size;
in vec4 color;
in vec4 texture_uv;
in float rotation; in float rotation;
out vec4 geo_size; out vec4 vertex_colors;
out vec4 geo_color; out vec3 texture_coords;
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;
uniform WindowBlock uniform WindowBlock
{ {
@ -111,102 +95,52 @@ geometry_source = """#version 150
mat4 view; mat4 view;
} window; } 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 void main()
// shader we need to define the inputs from it as arrays. {
// In our instance we just take single values (points) m_scale[0][0] = scale.x;
in vec4 geo_size[]; m_scale[1][1] = scale.y;
in vec4 geo_color[]; m_translate[3][0] = translate.x;
in vec4 geo_tex_coords[]; m_translate[3][1] = translate.y;
in float geo_rotation[]; 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; gl_Position = window.projection * window.view * m_translate * m_rotation * m_scale * vec4(position, 1.0);
out vec4 frag_color;
void main() { vertex_colors = colors;
texture_coords = tex_coords;
// 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();
} }
""" """
fragment_source = """#version 150 fragment_source = """#version 150 core
in vec2 uv; in vec4 vertex_colors;
in vec4 frag_color; in vec3 texture_coords;
out vec4 final_color; out vec4 final_colors;
uniform sampler2D sprite_texture; uniform sampler2D sprite_texture;
void main() { void main()
final_color = texture(sprite_texture, uv) * frag_color; {
final_colors = texture(sprite_texture, texture_coords.xy) * vertex_colors;
} }
""" """
fragment_array_source = """#version 150 core fragment_array_source = """#version 150 core
in vec2 uv; in vec4 vertex_colors;
in vec4 frag_color; in vec3 texture_coords;
out vec4 final_colors; out vec4 final_colors;
uniform sampler2DArray sprite_texture; uniform sampler2DArray sprite_texture;
void main() 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: try:
return pyglet.gl.current_context.pyglet_sprite_default_shader return pyglet.gl.current_context.pyglet_sprite_default_shader
except AttributeError: except AttributeError:
vert_shader = graphics.shader.Shader(vertex_source, 'vertex') _default_vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
geom_shader = graphics.shader.Shader(geometry_source, 'geometry') _default_frag_shader = graphics.shader.Shader(fragment_source, 'fragment')
frag_shader = graphics.shader.Shader(fragment_source, 'fragment') default_shader_program = graphics.shader.ShaderProgram(_default_vert_shader, _default_frag_shader)
default_shader_program = graphics.shader.ShaderProgram(vert_shader, geom_shader, frag_shader)
pyglet.gl.current_context.pyglet_sprite_default_shader = default_shader_program pyglet.gl.current_context.pyglet_sprite_default_shader = default_shader_program
return pyglet.gl.current_context.pyglet_sprite_default_shader return pyglet.gl.current_context.pyglet_sprite_default_shader
@ -227,10 +160,9 @@ def get_default_array_shader():
try: try:
return pyglet.gl.current_context.pyglet_sprite_default_array_shader return pyglet.gl.current_context.pyglet_sprite_default_array_shader
except AttributeError: except AttributeError:
vert_shader = graphics.shader.Shader(vertex_source, 'vertex') _default_vert_shader = graphics.shader.Shader(vertex_source, 'vertex')
geom_shader = graphics.shader.Shader(geometry_source, 'geometry') _default_array_frag_shader = graphics.shader.Shader(fragment_array_source, 'fragment')
frag_shader = graphics.shader.Shader(fragment_array_source, 'fragment') default_shader_program = graphics.shader.ShaderProgram(_default_vert_shader, _default_array_frag_shader)
default_shader_program = graphics.shader.ShaderProgram(vert_shader, geom_shader, frag_shader)
pyglet.gl.current_context.pyglet_sprite_default_array_shader = default_shader_program pyglet.gl.current_context.pyglet_sprite_default_array_shader = default_shader_program
return pyglet.gl.current_context.pyglet_sprite_default_array_shader return pyglet.gl.current_context.pyglet_sprite_default_array_shader
@ -312,7 +244,8 @@ class Sprite(event.EventDispatcher):
_frame_index = 0 _frame_index = 0
_paused = False _paused = False
_rotation = 0 _rotation = 0
_rgba = [255, 255, 255, 255] _opacity = 255
_rgb = (255, 255, 255)
_scale = 1.0 _scale = 1.0
_scale_x = 1.0 _scale_x = 1.0
_scale_y = 1.0 _scale_y = 1.0
@ -326,8 +259,7 @@ class Sprite(event.EventDispatcher):
blend_dest=GL_ONE_MINUS_SRC_ALPHA, blend_dest=GL_ONE_MINUS_SRC_ALPHA,
batch=None, batch=None,
group=None, group=None,
subpixel=False, subpixel=False):
program=None):
"""Create a sprite. """Create a sprite.
:Parameters: :Parameters:
@ -352,11 +284,6 @@ class Sprite(event.EventDispatcher):
`subpixel` : bool `subpixel` : bool
Allow floating-point coordinates for the sprite. By default, Allow floating-point coordinates for the sprite. By default,
coordinates are restricted to integer values. 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._x = x
self._y = y self._y = y
@ -372,46 +299,26 @@ class Sprite(event.EventDispatcher):
else: else:
self._texture = img.get_texture() 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._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._group = self.group_class(self._texture, blend_src, blend_dest, self.program, group)
self._subpixel = subpixel self._subpixel = subpixel
self._create_vertex_list() 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 @property
def program(self): def program(self):
return self._program if isinstance(self._img, image.TextureArrayRegion):
program = get_default_array_shader()
else:
program = get_default_shader()
@program.setter return program
def program(self, program):
if self._program == program: def __del__(self):
return try:
self._group = self.group_class(self._texture, if self._vertex_list is not None:
self._group.blend_src, self._vertex_list.delete()
self._group.blend_dest, except:
program, pass
self._user_group)
self._batch.migrate(self._vertex_list, GL_POINTS, self._group, self._batch)
self._program = program
def delete(self): def delete(self):
"""Force immediate removal of the sprite from video memory. """Force immediate removal of the sprite from video memory.
@ -425,6 +332,8 @@ class Sprite(event.EventDispatcher):
self._vertex_list.delete() self._vertex_list.delete()
self._vertex_list = None self._vertex_list = None
self._texture = None self._texture = None
# Easy way to break circular reference, speeds up GC
self._group = None self._group = None
def _animate(self, dt): def _animate(self, dt):
@ -464,7 +373,7 @@ class Sprite(event.EventDispatcher):
return return
if batch is not None and self._batch is not None: 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 self._batch = batch
else: else:
self._vertex_list.delete() self._vertex_list.delete()
@ -491,7 +400,7 @@ class Sprite(event.EventDispatcher):
self._group.blend_dest, self._group.blend_dest,
self._group.program, self._group.program,
group) 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 @property
def image(self): def image(self):
@ -519,6 +428,7 @@ class Sprite(event.EventDispatcher):
clock.schedule_once(self._animate, self._next_dt) clock.schedule_once(self._animate, self._next_dt)
else: else:
self._set_texture(img.get_texture()) self._set_texture(img.get_texture())
self._update_position()
def _set_texture(self, texture): def _set_texture(self, texture):
if texture.id is not self._texture.id: if texture.id is not self._texture.id:
@ -531,9 +441,35 @@ class Sprite(event.EventDispatcher):
self._texture = texture self._texture = texture
self._create_vertex_list() self._create_vertex_list()
else: else:
self._vertex_list.texture_uv[:] = texture.uv self._vertex_list.tex_coords[:] = texture.tex_coords
self._texture = texture 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 @property
def position(self): def position(self):
"""The (x, y, z) coordinates of the sprite, as a tuple. """The (x, y, z) coordinates of the sprite, as a tuple.
@ -551,7 +487,7 @@ class Sprite(event.EventDispatcher):
@position.setter @position.setter
def position(self, position): def position(self, position):
self._x, self._y, self._z = position self._x, self._y, self._z = position
self._vertex_list.position[:] = position self._vertex_list.translate[:] = position * 4
@property @property
def x(self): def x(self):
@ -564,7 +500,7 @@ class Sprite(event.EventDispatcher):
@x.setter @x.setter
def x(self, x): def x(self, x):
self._x = x self._x = x
self._vertex_list.position[:] = x, self._y, self._z self._vertex_list.translate[:] = (x, self._y, self._z) * 4
@property @property
def y(self): def y(self):
@ -577,7 +513,7 @@ class Sprite(event.EventDispatcher):
@y.setter @y.setter
def y(self, y): def y(self, y):
self._y = y self._y = y
self._vertex_list.position[:] = self._x, y, self._z self._vertex_list.translate[:] = (self._x, y, self._z) * 4
@property @property
def z(self): def z(self):
@ -590,7 +526,7 @@ class Sprite(event.EventDispatcher):
@z.setter @z.setter
def z(self, z): def z(self, z):
self._z = z self._z = z
self._vertex_list.position[:] = self._x, self._y, z self._vertex_list.translate[:] = (self._x, self._y, z) * 4
@property @property
def rotation(self): def rotation(self):
@ -606,7 +542,7 @@ class Sprite(event.EventDispatcher):
@rotation.setter @rotation.setter
def rotation(self, rotation): def rotation(self, rotation):
self._rotation = rotation self._rotation = rotation
self._vertex_list.rotation[0] = self._rotation self._vertex_list.rotation[:] = (self._rotation,) * 4
@property @property
def scale(self): def scale(self):
@ -622,7 +558,7 @@ class Sprite(event.EventDispatcher):
@scale.setter @scale.setter
def scale(self, scale): def scale(self, scale):
self._scale = 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 @property
def scale_x(self): def scale_x(self):
@ -638,7 +574,7 @@ class Sprite(event.EventDispatcher):
@scale_x.setter @scale_x.setter
def scale_x(self, scale_x): def scale_x(self, scale_x):
self._scale_x = 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 @property
def scale_y(self): def scale_y(self):
@ -654,7 +590,7 @@ class Sprite(event.EventDispatcher):
@scale_y.setter @scale_y.setter
def scale_y(self, scale_y): def scale_y(self, scale_y):
self._scale_y = 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): 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. """Simultaneously change the position, rotation or scale.
@ -693,11 +629,11 @@ class Sprite(event.EventDispatcher):
translations_outdated = True translations_outdated = True
if translations_outdated: 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: if rotation is not None and rotation != self._rotation:
self._rotation = rotation self._rotation = rotation
self._vertex_list.rotation[:] = rotation self._vertex_list.rotation[:] = (rotation,) * 4
scales_outdated = False scales_outdated = False
@ -713,7 +649,7 @@ class Sprite(event.EventDispatcher):
scales_outdated = True scales_outdated = True
if scales_outdated: 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 @property
def width(self): def width(self):
@ -755,12 +691,12 @@ class Sprite(event.EventDispatcher):
:type: int :type: int
""" """
return self._rgba[3] return self._opacity
@opacity.setter @opacity.setter
def opacity(self, opacity): def opacity(self, opacity):
self._rgba[3] = opacity self._opacity = opacity
self._vertex_list.color[:] = self._rgba self._vertex_list.colors[:] = (*self._rgb, int(self._opacity)) * 4
@property @property
def color(self): def color(self):
@ -774,12 +710,12 @@ class Sprite(event.EventDispatcher):
:type: (int, int, int) :type: (int, int, int)
""" """
return self._rgba[:3] return self._rgb
@color.setter @color.setter
def color(self, rgb): def color(self, rgb):
self._rgba[:3] = list(map(int, rgb)) self._rgb = list(map(int, rgb))
self._vertex_list.color[:] = self._rgba self._vertex_list.colors[:] = (*self._rgb, int(self._opacity)) * 4
@property @property
def visible(self): def visible(self):
@ -792,7 +728,7 @@ class Sprite(event.EventDispatcher):
@visible.setter @visible.setter
def visible(self, visible): def visible(self, visible):
self._visible = visible self._visible = visible
self._vertex_list.texture_uv[:] = (0, 0, 0, 0) if not visible else self._texture.uv self._update_position()
@property @property
def paused(self): def paused(self):
@ -846,16 +782,9 @@ class Sprite(event.EventDispatcher):
efficiently. efficiently.
""" """
self._group.set_state_recursive() self._group.set_state_recursive()
self._vertex_list.draw(GL_POINTS) self._vertex_list.draw(GL_TRIANGLES)
self._group.unset_state_recursive() 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: if _is_pyglet_doc_run:
def on_animation_end(self): def on_animation_end(self):
"""The sprite animation reached the final frame. """The sprite animation reached the final frame.
@ -869,3 +798,46 @@ class Sprite(event.EventDispatcher):
Sprite.register_event_type('on_animation_end') 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 { pub mod python_class {
use pyo3::class::basic::CompareOp; use pyo3::class::basic::CompareOp;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PySlice;
use crate::math::matrix::{Matrix3, Matrix4}; use crate::math::matrix::{Matrix3, Matrix4};
use crate::math::vector::{Vector2, Vector3, Vector4, VectorTrait}; 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); 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 and setter
#[getter] #[getter]