这是什么? 新的pyglet feature branch? merge一下!
This commit is contained in:
parent
2f99b527d0
commit
3c341103ad
@ -329,6 +329,7 @@ class _ModuleProxy:
|
|||||||
# Lazily load all modules, except if performing
|
# Lazily load all modules, except if performing
|
||||||
# type checking or code inspection.
|
# type checking or code inspection.
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from . import animation
|
||||||
from . import app
|
from . import app
|
||||||
from . import canvas
|
from . import canvas
|
||||||
from . import clock
|
from . import clock
|
||||||
@ -349,6 +350,7 @@ if TYPE_CHECKING:
|
|||||||
from . import text
|
from . import text
|
||||||
from . import window
|
from . import window
|
||||||
else:
|
else:
|
||||||
|
animation = _ModuleProxy('animation')
|
||||||
app = _ModuleProxy('app')
|
app = _ModuleProxy('app')
|
||||||
canvas = _ModuleProxy('canvas')
|
canvas = _ModuleProxy('canvas')
|
||||||
clock = _ModuleProxy('clock')
|
clock = _ModuleProxy('clock')
|
||||||
|
142
libs/pyglet/animation.py
Normal file
142
libs/pyglet/animation.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
"""Animations
|
||||||
|
|
||||||
|
Animations can be used by the :py:class:`~pyglet.sprite.Sprite` class in place
|
||||||
|
of static images. They are essentially containers for individual image frames,
|
||||||
|
with a duration per frame. They can be infinitely looping, or stop at the last
|
||||||
|
frame. You can load Animations from disk, such as from GIF files::
|
||||||
|
|
||||||
|
ani = pyglet.resource.animation('walking.gif')
|
||||||
|
sprite = pyglet.sprite.Sprite(img=ani)
|
||||||
|
|
||||||
|
Alternatively, you can create your own Animations from a sequence of images
|
||||||
|
by using the :py:meth:`~Animation.from_image_sequence` method::
|
||||||
|
|
||||||
|
images = [pyglet.resource.image('walk_a.png'),
|
||||||
|
pyglet.resource.image('walk_b.png'),
|
||||||
|
pyglet.resource.image('walk_c.png')]
|
||||||
|
|
||||||
|
ani = pyglet.image.Animation.from_image_sequence(images, duration=0.1, loop=True)
|
||||||
|
|
||||||
|
You can also use an :py:class:`pyglet.image.ImageGrid`, which is iterable::
|
||||||
|
|
||||||
|
sprite_sheet = pyglet.resource.image('my_sprite_sheet.png')
|
||||||
|
image_grid = pyglet.image.ImageGrid(sprite_sheet, rows=1, columns=5)
|
||||||
|
|
||||||
|
ani = pyglet.animation.Animation.from_image_sequence(image_grid, duration=0.1)
|
||||||
|
|
||||||
|
In the above examples, all the Animation Frames have the same duration.
|
||||||
|
If you wish to adjust this, you can manually create the Animation from a list of
|
||||||
|
:py:class:`~AnimationFrame`::
|
||||||
|
|
||||||
|
image_a = pyglet.resource.image('walk_a.png')
|
||||||
|
image_b = pyglet.resource.image('walk_b.png')
|
||||||
|
image_c = pyglet.resource.image('walk_c.png')
|
||||||
|
|
||||||
|
frame_a = AnimationFrame(image_a, duration=0.1)
|
||||||
|
frame_b = AnimationFrame(image_b, duration=0.2)
|
||||||
|
frame_c = AnimationFrame(image_c, duration=0.1)
|
||||||
|
|
||||||
|
ani = pyglet.image.Animation(frames=[frame_a, frame_b, frame_c])
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyglet import clock as _clock
|
||||||
|
from pyglet import event as _event
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationController(_event.EventDispatcher):
|
||||||
|
|
||||||
|
_frame_index: int = 0
|
||||||
|
_next_dt: float = 0.0
|
||||||
|
_paused: bool = False
|
||||||
|
|
||||||
|
_animation: 'Animation'
|
||||||
|
|
||||||
|
def _animate(self, dt):
|
||||||
|
"""
|
||||||
|
Subclasses of AnimationController should provide their own
|
||||||
|
_animate method. This method should determine
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def paused(self) -> bool:
|
||||||
|
"""Pause/resume the Animation."""
|
||||||
|
return self._paused
|
||||||
|
|
||||||
|
@paused.setter
|
||||||
|
def paused(self, pause):
|
||||||
|
if not self._animation or pause == self._paused:
|
||||||
|
return
|
||||||
|
if pause is True:
|
||||||
|
_clock.unschedule(self._animate)
|
||||||
|
else:
|
||||||
|
frame = self._animation.frames[self._frame_index]
|
||||||
|
self._next_dt = frame.duration
|
||||||
|
if self._next_dt:
|
||||||
|
_clock.schedule_once(self._animate, self._next_dt)
|
||||||
|
self._paused = pause
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frame_index(self) -> int:
|
||||||
|
"""The current AnimationFrame."""
|
||||||
|
return self._frame_index
|
||||||
|
|
||||||
|
@frame_index.setter
|
||||||
|
def frame_index(self, index):
|
||||||
|
# Bound to available number of frames
|
||||||
|
if self._animation is None:
|
||||||
|
return
|
||||||
|
self._frame_index = max(0, min(index, len(self._animation.frames)-1))
|
||||||
|
|
||||||
|
|
||||||
|
AnimationController.register_event_type('on_animation_end')
|
||||||
|
|
||||||
|
|
||||||
|
class Animation:
|
||||||
|
"""Sequence of AnimationFrames.
|
||||||
|
|
||||||
|
If no frames of the animation have a duration of ``None``, the animation
|
||||||
|
loops continuously; otherwise the animation stops at the first frame with
|
||||||
|
duration of ``None``.
|
||||||
|
|
||||||
|
:Ivariables:
|
||||||
|
`frames` : list of `~pyglet.animation.AnimationFrame`
|
||||||
|
The frames that make up the animation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = 'frames'
|
||||||
|
|
||||||
|
def __init__(self, frames: list):
|
||||||
|
"""Create an animation directly from a list of frames."""
|
||||||
|
assert len(frames)
|
||||||
|
self.frames = frames
|
||||||
|
|
||||||
|
def get_duration(self) -> float:
|
||||||
|
"""Get the total duration of the animation in seconds."""
|
||||||
|
return sum([frame.duration for frame in self.frames if frame.duration is not None])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_sequence(cls, sequence: list, duration: float, loop: bool = True):
|
||||||
|
"""Create an animation from a list of objects and a constant framerate."""
|
||||||
|
frames = [AnimationFrame(image, duration) for image in sequence]
|
||||||
|
if not loop:
|
||||||
|
frames[-1].duration = None
|
||||||
|
return cls(frames)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Animation(frames={0})".format(len(self.frames))
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationFrame:
|
||||||
|
"""A single frame of an animation."""
|
||||||
|
|
||||||
|
__slots__ = 'data', 'duration'
|
||||||
|
|
||||||
|
def __init__(self, data, duration):
|
||||||
|
self.data = data
|
||||||
|
self.duration = duration
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"AnimationFrame({self.data}, duration={self.duration})"
|
@ -1,26 +1,14 @@
|
|||||||
from pyglet.app.base import PlatformEventLoop
|
from pyglet.app.base import PlatformEventLoop
|
||||||
from pyglet.libs.darwin import cocoapy
|
from pyglet.libs.darwin import cocoapy, AutoReleasePool
|
||||||
|
|
||||||
NSApplication = cocoapy.ObjCClass('NSApplication')
|
NSApplication = cocoapy.ObjCClass('NSApplication')
|
||||||
NSMenu = cocoapy.ObjCClass('NSMenu')
|
NSMenu = cocoapy.ObjCClass('NSMenu')
|
||||||
NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
|
NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
|
||||||
NSAutoreleasePool = cocoapy.ObjCClass('NSAutoreleasePool')
|
|
||||||
NSDate = cocoapy.ObjCClass('NSDate')
|
NSDate = cocoapy.ObjCClass('NSDate')
|
||||||
NSDate.dateWithTimeIntervalSinceNow_.no_cached_return()
|
|
||||||
NSDate.distantFuture.no_cached_return()
|
|
||||||
|
|
||||||
NSEvent = cocoapy.ObjCClass('NSEvent')
|
NSEvent = cocoapy.ObjCClass('NSEvent')
|
||||||
NSUserDefaults = cocoapy.ObjCClass('NSUserDefaults')
|
NSUserDefaults = cocoapy.ObjCClass('NSUserDefaults')
|
||||||
|
|
||||||
|
|
||||||
class AutoReleasePool:
|
|
||||||
def __enter__(self):
|
|
||||||
self.pool = NSAutoreleasePool.alloc().init()
|
|
||||||
return self.pool
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
self.pool.drain()
|
|
||||||
del self.pool
|
|
||||||
|
|
||||||
|
|
||||||
def add_menu_item(menu, title, action, key):
|
def add_menu_item(menu, title, action, key):
|
||||||
@ -33,8 +21,6 @@ def add_menu_item(menu, title, action, key):
|
|||||||
menu.addItem_(menuItem)
|
menu.addItem_(menuItem)
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
title.release()
|
|
||||||
key.release()
|
|
||||||
menuItem.release()
|
menuItem.release()
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,53 +5,6 @@ Shaders and Buffers. It also provides classes for highly performant batched
|
|||||||
rendering and grouping.
|
rendering and grouping.
|
||||||
|
|
||||||
See the :ref:`guide_graphics` for details on how to use this graphics API.
|
See the :ref:`guide_graphics` for details on how to use this graphics API.
|
||||||
|
|
||||||
Batches and groups
|
|
||||||
==================
|
|
||||||
|
|
||||||
Developers can make use of :py:class:`~pyglet.graphics.Batch` and
|
|
||||||
:py:class:`~pyglet.graphics.Group` objects to improve performance when
|
|
||||||
rendering a large number of objects.
|
|
||||||
|
|
||||||
The :py:class:`~pyglet.sprite.Sprite`, :py:func:`~pyglet.text.Label`,
|
|
||||||
:py:func:`~pyglet.text.layout.TextLayout`, and other classes all accept a
|
|
||||||
``batch`` and ``group`` parameter in their constructors. A Batch manages
|
|
||||||
a set of objects that will be drawn all at once, and a Group can be used
|
|
||||||
to set OpenGL state and further sort the draw operation.
|
|
||||||
|
|
||||||
The following example creates a batch, adds two sprites to the batch, and then
|
|
||||||
draws the entire batch::
|
|
||||||
|
|
||||||
batch = pyglet.graphics.Batch()
|
|
||||||
car = pyglet.sprite.Sprite(car_image, batch=batch)
|
|
||||||
boat = pyglet.sprite.Sprite(boat_image, batch=batch)
|
|
||||||
|
|
||||||
def on_draw():
|
|
||||||
batch.draw()
|
|
||||||
|
|
||||||
Drawing a complete Batch is much faster than drawing the items in the batch
|
|
||||||
individually, especially when those items belong to a common group.
|
|
||||||
|
|
||||||
Groups describe the OpenGL state required for an item. This is for the most
|
|
||||||
part managed by the sprite, text, and other classes, however you can also use
|
|
||||||
custom groups to ensure items are drawn in a particular order. For example, the
|
|
||||||
following example adds a background sprite which is guaranteed to be drawn
|
|
||||||
before the car and the boat::
|
|
||||||
|
|
||||||
batch = pyglet.graphics.Batch()
|
|
||||||
background = pyglet.graphics.Group(order=0)
|
|
||||||
foreground = pyglet.graphics.Group(order=1)
|
|
||||||
|
|
||||||
background = pyglet.sprite.Sprite(background_image, batch=batch, group=background)
|
|
||||||
car = pyglet.sprite.Sprite(car_image, batch=batch, group=foreground)
|
|
||||||
boat = pyglet.sprite.Sprite(boat_image, batch=batch, group=foreground)
|
|
||||||
|
|
||||||
def on_draw():
|
|
||||||
batch.draw()
|
|
||||||
|
|
||||||
It's preferable to manage pyglet objects within as few batches as possible. If
|
|
||||||
the drawing of sprites or text objects need to be interleaved with other
|
|
||||||
drawing that does not use the graphics API, multiple batches will be required.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
@ -255,15 +208,31 @@ def get_default_shader():
|
|||||||
|
|
||||||
|
|
||||||
class Batch:
|
class Batch:
|
||||||
"""Manage a collection of vertex lists for batched rendering.
|
"""Manage a collection of drawables for batched rendering.
|
||||||
|
|
||||||
Vertex lists are added to a :py:class:`~pyglet.graphics.Batch` using the
|
Many drawable pyglet objects accept an optional `Batch` argument in their
|
||||||
`add` and `add_indexed` methods. An optional group can be specified along
|
constructors. By giving a `Batch` to multiple objects, you can tell pyglet
|
||||||
with the vertex list, which gives the OpenGL state required for its rendering.
|
that you expect to draw all of these objects at once, so it can optimise its
|
||||||
Vertex lists with shared mode and group are allocated into adjacent areas of
|
use of OpenGL. Hence, drawing a `Batch` is often much faster than drawing
|
||||||
memory and sent to the graphics card in a single operation.
|
each contained drawable separately.
|
||||||
|
|
||||||
Call `VertexList.delete` to remove a vertex list from the batch.
|
The following example creates a batch, adds two sprites to the batch, and
|
||||||
|
then draws the entire batch::
|
||||||
|
|
||||||
|
batch = pyglet.graphics.Batch()
|
||||||
|
car = pyglet.sprite.Sprite(car_image, batch=batch)
|
||||||
|
boat = pyglet.sprite.Sprite(boat_image, batch=batch)
|
||||||
|
|
||||||
|
def on_draw():
|
||||||
|
batch.draw()
|
||||||
|
|
||||||
|
While any drawables can be added to a `Batch`, only those with the same
|
||||||
|
draw mode, shader program, and group can be optimised together.
|
||||||
|
|
||||||
|
Internally, a `Batch` manages a set of VertexDomains along with
|
||||||
|
information about how the domains are to be drawn. To implement batching on
|
||||||
|
a custom drawable, get your vertex domains from the given batch instead of
|
||||||
|
setting them up yourself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -325,6 +294,7 @@ class Batch:
|
|||||||
vertex_list.migrate(domain)
|
vertex_list.migrate(domain)
|
||||||
|
|
||||||
def get_domain(self, indexed, mode, group, program, attributes):
|
def get_domain(self, indexed, mode, group, program, attributes):
|
||||||
|
"""Get, or create, the vertex domain corresponding to the given arguments."""
|
||||||
if group is None:
|
if group is None:
|
||||||
group = ShaderGroup(program=program)
|
group = ShaderGroup(program=program)
|
||||||
|
|
||||||
@ -496,27 +466,43 @@ class Batch:
|
|||||||
class Group:
|
class Group:
|
||||||
"""Group of common OpenGL state.
|
"""Group of common OpenGL state.
|
||||||
|
|
||||||
Before a VertexList is rendered, its Group's OpenGL state is set.
|
`Group` provides extra control over how drawables are handled within a
|
||||||
This includes binding textures, shaders, or setting any other parameters.
|
`Batch`. When a batch draws a drawable, it ensures its group's state is set;
|
||||||
"""
|
this can include binding textures, shaders, or setting any other parameters.
|
||||||
def __init__(self, order=0, parent=None):
|
It also sorts the groups before drawing.
|
||||||
"""Create a Group.
|
|
||||||
|
In the following example, the background sprite is guaranteed to be drawn
|
||||||
|
before the car and the boat::
|
||||||
|
|
||||||
|
batch = pyglet.graphics.Batch()
|
||||||
|
background = pyglet.graphics.Group(order=0)
|
||||||
|
foreground = pyglet.graphics.Group(order=1)
|
||||||
|
|
||||||
|
background = pyglet.sprite.Sprite(background_image, batch=batch, group=background)
|
||||||
|
car = pyglet.sprite.Sprite(car_image, batch=batch, group=foreground)
|
||||||
|
boat = pyglet.sprite.Sprite(boat_image, batch=batch, group=foreground)
|
||||||
|
|
||||||
|
def on_draw():
|
||||||
|
batch.draw()
|
||||||
|
|
||||||
:Parameters:
|
:Parameters:
|
||||||
`order` : int
|
`order` : int
|
||||||
Set the order to render above or below other Groups.
|
Set the order to render above or below other Groups.
|
||||||
|
Lower orders are drawn first.
|
||||||
`parent` : `~pyglet.graphics.Group`
|
`parent` : `~pyglet.graphics.Group`
|
||||||
Group to contain this Group; its state will be set before this
|
Group to contain this Group; its state will be set before this
|
||||||
Group's state.
|
Group's state.
|
||||||
|
|
||||||
:Ivariables:
|
:Variables:
|
||||||
`visible` : bool
|
`visible` : bool
|
||||||
Determines whether this Group is visible in any of the Batches
|
Determines whether this Group is visible in any of the Batches
|
||||||
it is assigned to. If False, objects in this Group will not
|
it is assigned to. If ``False``, objects in this Group will not
|
||||||
be rendered.
|
be rendered.
|
||||||
`batches` : list
|
`batches` : list
|
||||||
Read Only. A list of which Batches this Group is a part of.
|
Read Only. A list of which Batches this Group is a part of.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, order=0, parent=None):
|
||||||
|
|
||||||
self._order = order
|
self._order = order
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._visible = True
|
self._visible = True
|
||||||
|
@ -645,19 +645,16 @@ class ShaderSource:
|
|||||||
|
|
||||||
|
|
||||||
class Shader:
|
class Shader:
|
||||||
"""OpenGL Shader object"""
|
"""OpenGL shader.
|
||||||
|
|
||||||
def __init__(self, source_string: str, shader_type: str):
|
|
||||||
"""Create an instance of a Shader object.
|
|
||||||
|
|
||||||
Shader source code should be provided as a Python string.
|
|
||||||
The shader_type should be a string indicating the type.
|
|
||||||
Valid types are: 'compute', 'fragment', 'geometry', 'tesscontrol',
|
|
||||||
'tessevaluation', and 'vertex'.
|
|
||||||
|
|
||||||
Shader objects are compiled on instantiation.
|
Shader objects are compiled on instantiation.
|
||||||
You can reuse a Shader object in multiple `ShaderProgram`s.
|
You can reuse a Shader object in multiple ShaderPrograms.
|
||||||
|
|
||||||
|
`shader_type` is one of ``'compute'``, ``'fragment'``, ``'geometry'``,
|
||||||
|
``'tesscontrol'``, ``'tessevaluation'``, or ``'vertex'``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, source_string: str, shader_type: str):
|
||||||
self._id = None
|
self._id = None
|
||||||
self.type = shader_type
|
self.type = shader_type
|
||||||
|
|
||||||
@ -735,12 +732,11 @@ class Shader:
|
|||||||
|
|
||||||
|
|
||||||
class ShaderProgram:
|
class ShaderProgram:
|
||||||
"""OpenGL Shader Program"""
|
"""OpenGL shader program."""
|
||||||
|
|
||||||
__slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__'
|
__slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__'
|
||||||
|
|
||||||
def __init__(self, *shaders: Shader):
|
def __init__(self, *shaders: Shader):
|
||||||
"""Create an OpenGL ShaderProgram from one or more Shader objects."""
|
|
||||||
assert shaders, "At least one Shader object is required."
|
assert shaders, "At least one Shader object is required."
|
||||||
self._id = _link_program(*shaders)
|
self._id = _link_program(*shaders)
|
||||||
self._context = pyglet.gl.current_context
|
self._context = pyglet.gl.current_context
|
||||||
@ -825,6 +821,7 @@ class ShaderProgram:
|
|||||||
`mode` : int
|
`mode` : int
|
||||||
OpenGL drawing mode enumeration; for example, one of
|
OpenGL drawing mode enumeration; for example, one of
|
||||||
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
||||||
|
This determines how the list is drawn in the given batch.
|
||||||
`batch` : `~pyglet.graphics.Batch`
|
`batch` : `~pyglet.graphics.Batch`
|
||||||
Batch to add the VertexList to, or ``None`` if a Batch will not be used.
|
Batch to add the VertexList to, or ``None`` if a Batch will not be used.
|
||||||
Using a Batch is strongly recommended.
|
Using a Batch is strongly recommended.
|
||||||
@ -867,6 +864,7 @@ class ShaderProgram:
|
|||||||
`mode` : int
|
`mode` : int
|
||||||
OpenGL drawing mode enumeration; for example, one of
|
OpenGL drawing mode enumeration; for example, one of
|
||||||
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
||||||
|
This determines how the list is drawn in the given batch.
|
||||||
`indices` : sequence of int
|
`indices` : sequence of int
|
||||||
Sequence of integers giving indices into the vertex list.
|
Sequence of integers giving indices into the vertex list.
|
||||||
`batch` : `~pyglet.graphics.Batch`
|
`batch` : `~pyglet.graphics.Batch`
|
||||||
|
@ -97,19 +97,17 @@ import re
|
|||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
from io import open, BytesIO
|
from io import open
|
||||||
|
|
||||||
import pyglet
|
import pyglet
|
||||||
|
|
||||||
from pyglet.gl import *
|
|
||||||
from pyglet.gl import gl_info
|
|
||||||
from pyglet.util import asbytes
|
from pyglet.util import asbytes
|
||||||
|
from pyglet.animation import Animation
|
||||||
|
|
||||||
from .codecs import ImageEncodeException, ImageDecodeException
|
|
||||||
from .codecs import registry as _codec_registry
|
from .codecs import registry as _codec_registry
|
||||||
from .codecs import add_default_codecs as _add_default_codecs
|
from .codecs import add_default_codecs as _add_default_codecs
|
||||||
|
from .codecs import ImageEncodeException, ImageDecodeException
|
||||||
|
|
||||||
from .animation import Animation, AnimationFrame
|
|
||||||
from .buffer import *
|
from .buffer import *
|
||||||
from . import atlas
|
from . import atlas
|
||||||
|
|
||||||
@ -449,7 +447,7 @@ class AbstractImageSequence:
|
|||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
"""
|
"""
|
||||||
return Animation.from_image_sequence(self, period, loop)
|
return Animation.from_sequence(self, period, loop)
|
||||||
|
|
||||||
def __getitem__(self, slice):
|
def __getitem__(self, slice):
|
||||||
"""Retrieve a (list of) image.
|
"""Retrieve a (list of) image.
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
"""2D Animations
|
|
||||||
|
|
||||||
Animations can be used by the :py:class:`~pyglet.sprite.Sprite` class in place
|
|
||||||
of static images. They are essentially containers for individual image frames,
|
|
||||||
with a duration per frame. They can be infinitely looping, or stop at the last
|
|
||||||
frame. You can load Animations from disk, such as from GIF files::
|
|
||||||
|
|
||||||
ani = pyglet.resource.animation('walking.gif')
|
|
||||||
sprite = pyglet.sprite.Sprite(img=ani)
|
|
||||||
|
|
||||||
Alternatively, you can create your own Animations from a sequence of images
|
|
||||||
by using the :py:meth:`~Animation.from_image_sequence` method::
|
|
||||||
|
|
||||||
images = [pyglet.resource.image('walk_a.png'),
|
|
||||||
pyglet.resource.image('walk_b.png'),
|
|
||||||
pyglet.resource.image('walk_c.png')]
|
|
||||||
|
|
||||||
ani = pyglet.image.Animation.from_image_sequence(images, duration=0.1, loop=True)
|
|
||||||
|
|
||||||
You can also use an :py:class:`pyglet.image.ImageGrid`, which is iterable::
|
|
||||||
|
|
||||||
sprite_sheet = pyglet.resource.image('my_sprite_sheet.png')
|
|
||||||
image_grid = pyglet.image.ImageGrid(sprite_sheet, rows=1, columns=5)
|
|
||||||
|
|
||||||
ani = pyglet.image.Animation.from_image_sequence(image_grid, duration=0.1)
|
|
||||||
|
|
||||||
In the above examples, all of the Animation Frames have the same duration.
|
|
||||||
If you wish to adjust this, you can manually create the Animation from a list of
|
|
||||||
:py:class:`~AnimationFrame`::
|
|
||||||
|
|
||||||
image_a = pyglet.resource.image('walk_a.png')
|
|
||||||
image_b = pyglet.resource.image('walk_b.png')
|
|
||||||
image_c = pyglet.resource.image('walk_c.png')
|
|
||||||
|
|
||||||
frame_a = pyglet.image.AnimationFrame(image_a, duration=0.1)
|
|
||||||
frame_b = pyglet.image.AnimationFrame(image_b, duration=0.2)
|
|
||||||
frame_c = pyglet.image.AnimationFrame(image_c, duration=0.1)
|
|
||||||
|
|
||||||
ani = pyglet.image.Animation(frames=[frame_a, frame_b, frame_c])
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Animation:
|
|
||||||
"""Sequence of images with timing information.
|
|
||||||
|
|
||||||
If no frames of the animation have a duration of ``None``, the animation
|
|
||||||
loops continuously; otherwise the animation stops at the first frame with
|
|
||||||
duration of ``None``.
|
|
||||||
|
|
||||||
:Ivariables:
|
|
||||||
`frames` : list of `~pyglet.image.AnimationFrame`
|
|
||||||
The frames that make up the animation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, frames):
|
|
||||||
"""Create an animation directly from a list of frames.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`frames` : list of `~pyglet.image.AnimationFrame`
|
|
||||||
The frames that make up the animation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert len(frames)
|
|
||||||
self.frames = frames
|
|
||||||
|
|
||||||
def add_to_texture_bin(self, texture_bin, border=0):
|
|
||||||
"""Add the images of the animation to a :py:class:`~pyglet.image.atlas.TextureBin`.
|
|
||||||
|
|
||||||
The animation frames are modified in-place to refer to the texture bin
|
|
||||||
regions.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`texture_bin` : `~pyglet.image.atlas.TextureBin`
|
|
||||||
Texture bin to upload animation frames into.
|
|
||||||
`border` : int
|
|
||||||
Leaves specified pixels of blank space around
|
|
||||||
each image frame when adding to the TextureBin.
|
|
||||||
|
|
||||||
"""
|
|
||||||
for frame in self.frames:
|
|
||||||
frame.image = texture_bin.add(frame.image, border)
|
|
||||||
|
|
||||||
def get_transform(self, flip_x=False, flip_y=False, rotate=0):
|
|
||||||
"""Create a copy of this animation applying a simple transformation.
|
|
||||||
|
|
||||||
The transformation is applied around the image's anchor point of
|
|
||||||
each frame. The texture data is shared between the original animation
|
|
||||||
and the transformed animation.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`flip_x` : bool
|
|
||||||
If True, the returned animation will be flipped horizontally.
|
|
||||||
`flip_y` : bool
|
|
||||||
If True, the returned animation will be flipped vertically.
|
|
||||||
`rotate` : int
|
|
||||||
Degrees of clockwise rotation of the returned animation. Only
|
|
||||||
90-degree increments are supported.
|
|
||||||
|
|
||||||
:rtype: :py:class:`~pyglet.image.Animation`
|
|
||||||
"""
|
|
||||||
frames = [AnimationFrame(frame.image.get_texture().get_transform(flip_x, flip_y, rotate),
|
|
||||||
frame.duration) for frame in self.frames]
|
|
||||||
return Animation(frames)
|
|
||||||
|
|
||||||
def get_duration(self):
|
|
||||||
"""Get the total duration of the animation in seconds.
|
|
||||||
|
|
||||||
:rtype: float
|
|
||||||
"""
|
|
||||||
return sum([frame.duration for frame in self.frames if frame.duration is not None])
|
|
||||||
|
|
||||||
def get_max_width(self):
|
|
||||||
"""Get the maximum image frame width.
|
|
||||||
|
|
||||||
This method is useful for determining texture space requirements: due
|
|
||||||
to the use of ``anchor_x`` the actual required playback area may be
|
|
||||||
larger.
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
return max([frame.image.width for frame in self.frames])
|
|
||||||
|
|
||||||
def get_max_height(self):
|
|
||||||
"""Get the maximum image frame height.
|
|
||||||
|
|
||||||
This method is useful for determining texture space requirements: due
|
|
||||||
to the use of ``anchor_y`` the actual required playback area may be
|
|
||||||
larger.
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
return max([frame.image.height for frame in self.frames])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_image_sequence(cls, sequence, duration, loop=True):
|
|
||||||
"""Create an animation from a list of images and a constant framerate.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`sequence` : list of `~pyglet.image.AbstractImage`
|
|
||||||
Images that make up the animation, in sequence.
|
|
||||||
`duration` : float
|
|
||||||
Number of seconds to display each image.
|
|
||||||
`loop` : bool
|
|
||||||
If True, the animation will loop continuously.
|
|
||||||
|
|
||||||
:rtype: :py:class:`~pyglet.image.Animation`
|
|
||||||
"""
|
|
||||||
frames = [AnimationFrame(image, duration) for image in sequence]
|
|
||||||
if not loop:
|
|
||||||
frames[-1].duration = None
|
|
||||||
return cls(frames)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "Animation(frames={0})".format(len(self.frames))
|
|
||||||
|
|
||||||
|
|
||||||
class AnimationFrame:
|
|
||||||
"""A single frame of an animation."""
|
|
||||||
|
|
||||||
__slots__ = 'image', 'duration'
|
|
||||||
|
|
||||||
def __init__(self, image, duration):
|
|
||||||
"""Create an animation frame from an image.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`image` : `~pyglet.image.AbstractImage`
|
|
||||||
The image of this frame.
|
|
||||||
`duration` : float
|
|
||||||
Number of seconds to display the frame, or ``None`` if it is
|
|
||||||
the last frame in the animation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.image = image
|
|
||||||
self.duration = duration
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "AnimationFrame({0}, duration={1})".format(self.image, self.duration)
|
|
@ -4,6 +4,7 @@ from pyglet.gl import *
|
|||||||
from pyglet.image import *
|
from pyglet.image import *
|
||||||
from pyglet.image.codecs import *
|
from pyglet.image.codecs import *
|
||||||
from pyglet.image.codecs import gif
|
from pyglet.image.codecs import gif
|
||||||
|
from pyglet.animation import AnimationFrame
|
||||||
|
|
||||||
import pyglet.lib
|
import pyglet.lib
|
||||||
import pyglet.window
|
import pyglet.window
|
||||||
|
@ -35,3 +35,15 @@ from .runtime import ObjCClass, ObjCInstance, ObjCSubclass
|
|||||||
|
|
||||||
from .cocoatypes import *
|
from .cocoatypes import *
|
||||||
from .cocoalibs import *
|
from .cocoalibs import *
|
||||||
|
|
||||||
|
NSAutoreleasePool = ObjCClass('NSAutoreleasePool')
|
||||||
|
|
||||||
|
class AutoReleasePool:
|
||||||
|
"""Helper context function to more easily manage NSAutoreleasePool"""
|
||||||
|
def __enter__(self):
|
||||||
|
self.pool = NSAutoreleasePool.alloc().init()
|
||||||
|
return self.pool
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.pool.drain()
|
||||||
|
del self.pool
|
||||||
|
@ -47,8 +47,7 @@ cf.CFAttributedStringCreate.argtypes = [CFAllocatorRef, c_void_p, c_void_p]
|
|||||||
# Core Foundation type to Python type conversion functions
|
# Core Foundation type to Python type conversion functions
|
||||||
|
|
||||||
def CFSTR(string):
|
def CFSTR(string):
|
||||||
return ObjCInstance(c_void_p(cf.CFStringCreateWithCString(
|
return cf.CFStringCreateWithCString(None, string.encode('utf8'), kCFStringEncodingUTF8)
|
||||||
None, string.encode('utf8'), kCFStringEncodingUTF8)))
|
|
||||||
|
|
||||||
# Other possible names for this method:
|
# Other possible names for this method:
|
||||||
# at, ampersat, arobe, apenstaartje (little monkey tail), strudel,
|
# at, ampersat, arobe, apenstaartje (little monkey tail), strudel,
|
||||||
@ -57,7 +56,7 @@ def CFSTR(string):
|
|||||||
# kukac (caterpillar).
|
# kukac (caterpillar).
|
||||||
def get_NSString(string):
|
def get_NSString(string):
|
||||||
"""Autoreleased version of CFSTR"""
|
"""Autoreleased version of CFSTR"""
|
||||||
return CFSTR(string).autorelease()
|
return ObjCInstance(c_void_p(CFSTR(string))).autorelease()
|
||||||
|
|
||||||
def cfstring_to_string(cfstring):
|
def cfstring_to_string(cfstring):
|
||||||
length = cf.CFStringGetLength(cfstring)
|
length = cf.CFStringGetLength(cfstring)
|
||||||
|
@ -409,7 +409,6 @@ objc.sel_isEqual.argtypes = [c_void_p, c_void_p]
|
|||||||
objc.sel_registerName.restype = c_void_p
|
objc.sel_registerName.restype = c_void_p
|
||||||
objc.sel_registerName.argtypes = [c_char_p]
|
objc.sel_registerName.argtypes = [c_char_p]
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Constants
|
# Constants
|
||||||
OBJC_ASSOCIATION_ASSIGN = 0 # Weak reference to the associated object.
|
OBJC_ASSOCIATION_ASSIGN = 0 # Weak reference to the associated object.
|
||||||
@ -480,6 +479,8 @@ def should_use_fpret(restype):
|
|||||||
# change these values. restype should be a ctypes type
|
# change these values. restype should be a ctypes type
|
||||||
# and argtypes should be a list of ctypes types for
|
# and argtypes should be a list of ctypes types for
|
||||||
# the arguments of the message only.
|
# the arguments of the message only.
|
||||||
|
# Note: kwarg 'argtypes' required if using args, or will fail on ARM64.
|
||||||
|
|
||||||
def send_message(receiver, selName, *args, **kwargs):
|
def send_message(receiver, selName, *args, **kwargs):
|
||||||
if isinstance(receiver, str):
|
if isinstance(receiver, str):
|
||||||
receiver = get_class(receiver)
|
receiver = get_class(receiver)
|
||||||
@ -717,7 +718,6 @@ class ObjCMethod:
|
|||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
"""Initialize with an Objective-C Method pointer. We then determine
|
"""Initialize with an Objective-C Method pointer. We then determine
|
||||||
the return type and argument type information of the method."""
|
the return type and argument type information of the method."""
|
||||||
self.cache = True
|
|
||||||
self.selector = c_void_p(objc.method_getName(method))
|
self.selector = c_void_p(objc.method_getName(method))
|
||||||
self.name = objc.sel_getName(self.selector)
|
self.name = objc.sel_getName(self.selector)
|
||||||
self.pyname = self.name.replace(b':', b'_')
|
self.pyname = self.name.replace(b':', b'_')
|
||||||
@ -734,7 +734,7 @@ class ObjCMethod:
|
|||||||
try:
|
try:
|
||||||
self.argtypes = [self.ctype_for_encoding(t) for t in self.argument_types]
|
self.argtypes = [self.ctype_for_encoding(t) for t in self.argument_types]
|
||||||
except:
|
except:
|
||||||
# print(f'no argtypes encoding for {self.name} ({self.argument_types})')
|
# print('no argtypes encoding for %s (%s)' % (self.name, self.argument_types))
|
||||||
self.argtypes = None
|
self.argtypes = None
|
||||||
# Get types for the return type.
|
# Get types for the return type.
|
||||||
try:
|
try:
|
||||||
@ -745,7 +745,7 @@ class ObjCMethod:
|
|||||||
else:
|
else:
|
||||||
self.restype = self.ctype_for_encoding(self.return_type)
|
self.restype = self.ctype_for_encoding(self.return_type)
|
||||||
except:
|
except:
|
||||||
# print(f'no restype encoding for {self.name} ({self.return_type})')
|
# print('no restype encoding for %s (%s)' % (self.name, self.return_type))
|
||||||
self.restype = None
|
self.restype = None
|
||||||
self.func = None
|
self.func = None
|
||||||
|
|
||||||
@ -803,7 +803,7 @@ class ObjCMethod:
|
|||||||
result = f(objc_id, self.selector, *args)
|
result = f(objc_id, self.selector, *args)
|
||||||
# Convert result to python type if it is a instance or class pointer.
|
# Convert result to python type if it is a instance or class pointer.
|
||||||
if self.restype == ObjCInstance:
|
if self.restype == ObjCInstance:
|
||||||
result = ObjCInstance(result, self.cache)
|
result = ObjCInstance(result)
|
||||||
elif self.restype == ObjCClass:
|
elif self.restype == ObjCClass:
|
||||||
result = ObjCClass(result)
|
result = ObjCClass(result)
|
||||||
return result
|
return result
|
||||||
@ -826,13 +826,6 @@ class ObjCBoundMethod:
|
|||||||
self.method = method
|
self.method = method
|
||||||
self.objc_id = objc_id
|
self.objc_id = objc_id
|
||||||
|
|
||||||
def no_cached_return(self):
|
|
||||||
"""Disables the return type from being registered in DeallocationObserver.
|
|
||||||
Some return types do not get observed and will cause a memory leak.
|
|
||||||
Ex: NDate return types can be __NSTaggedDate
|
|
||||||
"""
|
|
||||||
self.method.cache = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<ObjCBoundMethod %s (%s)>' % (self.method.name, self.objc_id)
|
return '<ObjCBoundMethod %s (%s)>' % (self.method.name, self.objc_id)
|
||||||
|
|
||||||
@ -967,8 +960,49 @@ class ObjCClass:
|
|||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class _AutoreleasepoolManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.current = 0 # Current Pool ID. 0 is Global and not removed.
|
||||||
|
self.pools = [None] # List of NSAutoreleasePools.
|
||||||
|
|
||||||
|
@property
|
||||||
|
def count(self):
|
||||||
|
"""Number of total pools. Not including global."""
|
||||||
|
return len(self.pools) - 1
|
||||||
|
|
||||||
|
def create(self, pool):
|
||||||
|
self.pools.append(pool)
|
||||||
|
self.current = self.pools.index(pool)
|
||||||
|
|
||||||
|
def delete(self, pool):
|
||||||
|
self.pools.remove(pool)
|
||||||
|
self.current = len(self.pools) - 1
|
||||||
|
|
||||||
|
|
||||||
|
_arp_manager = _AutoreleasepoolManager()
|
||||||
|
|
||||||
|
_dealloc_argtype = [c_void_p] # Just to prevent list creation every call.
|
||||||
|
|
||||||
|
def _set_dealloc_observer(objc_ptr):
|
||||||
|
# Create a DeallocationObserver and associate it with this object.
|
||||||
|
# When the Objective-C object is deallocated, the observer will remove
|
||||||
|
# the ObjCInstance corresponding to the object from the cached objects
|
||||||
|
# dictionary, effectively destroying the ObjCInstance.
|
||||||
|
observer = send_message('DeallocationObserver', 'alloc')
|
||||||
|
observer = send_message(observer, 'initWithObject:', objc_ptr, argtypes=_dealloc_argtype)
|
||||||
|
objc.objc_setAssociatedObject(objc_ptr, observer, observer, OBJC_ASSOCIATION_RETAIN)
|
||||||
|
|
||||||
|
# The observer is retained by the object we associate it to. We release
|
||||||
|
# the observer now so that it will be deallocated when the associated
|
||||||
|
# object is deallocated.
|
||||||
|
send_message(observer, 'release')
|
||||||
|
|
||||||
|
|
||||||
class ObjCInstance:
|
class ObjCInstance:
|
||||||
"""Python wrapper for an Objective-C instance."""
|
"""Python wrapper for an Objective-C instance."""
|
||||||
|
pool = 0 # What pool id this belongs in.
|
||||||
|
retained = False # If instance is kept even if pool is wiped.
|
||||||
|
|
||||||
_cached_objects = {}
|
_cached_objects = {}
|
||||||
|
|
||||||
@ -1004,17 +1038,16 @@ class ObjCInstance:
|
|||||||
if cache:
|
if cache:
|
||||||
cls._cached_objects[object_ptr.value] = objc_instance
|
cls._cached_objects[object_ptr.value] = objc_instance
|
||||||
|
|
||||||
# Create a DeallocationObserver and associate it with this object.
|
# Creation of NSAutoreleasePool instance does not technically mean it was allocated and initialized, but
|
||||||
# When the Objective-C object is deallocated, the observer will remove
|
# it's standard practice, so this should not be an issue.
|
||||||
# the ObjCInstance corresponding to the object from the cached objects
|
if objc_instance.objc_class.name == b"NSAutoreleasePool":
|
||||||
# dictionary, effectively destroying the ObjCInstance.
|
_arp_manager.create(objc_instance)
|
||||||
observer = send_message(send_message('DeallocationObserver', 'alloc'), 'initWithObject:', objc_instance)
|
objc_instance.pool = _arp_manager.current
|
||||||
objc.objc_setAssociatedObject(objc_instance, observer, observer, OBJC_ASSOCIATION_RETAIN)
|
_set_dealloc_observer(object_ptr)
|
||||||
|
elif _arp_manager.current:
|
||||||
# The observer is retained by the object we associate it to. We release
|
objc_instance.pool = _arp_manager.current
|
||||||
# the observer now so that it will be deallocated when the associated
|
else:
|
||||||
# object is deallocated.
|
_set_dealloc_observer(object_ptr)
|
||||||
send_message(observer, 'release')
|
|
||||||
|
|
||||||
return objc_instance
|
return objc_instance
|
||||||
|
|
||||||
@ -1048,12 +1081,16 @@ class ObjCInstance:
|
|||||||
# Otherwise raise an exception.
|
# Otherwise raise an exception.
|
||||||
raise AttributeError('ObjCInstance %s has no attribute %s' % (self.objc_class.name, name))
|
raise AttributeError('ObjCInstance %s has no attribute %s' % (self.objc_class.name, name))
|
||||||
|
|
||||||
|
|
||||||
def get_cached_instances():
|
def get_cached_instances():
|
||||||
"""For debug purposes, return a list of instance names.
|
"""For debug purposes, return a list of instance names.
|
||||||
Useful for debugging if an object is leaking."""
|
Useful for debugging if an object is leaking."""
|
||||||
return [obj.objc_class.name for obj in ObjCInstance._cached_objects.values()]
|
return [obj.objc_class.name for obj in ObjCInstance._cached_objects.values()]
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
|
||||||
def convert_method_arguments(encoding, args):
|
def convert_method_arguments(encoding, args):
|
||||||
"""Used by ObjCSubclass to convert Objective-C method arguments to
|
"""Used by ObjCSubclass to convert Objective-C method arguments to
|
||||||
Python values before passing them on to the Python-defined method."""
|
Python values before passing them on to the Python-defined method."""
|
||||||
@ -1183,8 +1220,9 @@ class ObjCSubclass:
|
|||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
def objc_method(objc_self, objc_cmd, *args):
|
def objc_method(objc_self, objc_cmd, *args):
|
||||||
py_self = ObjCInstance(objc_self)
|
py_self = ObjCInstance(objc_self, True)
|
||||||
py_self.objc_cmd = objc_cmd
|
py_self.objc_cmd = objc_cmd
|
||||||
|
py_self.retained = True
|
||||||
args = convert_method_arguments(encoding, args)
|
args = convert_method_arguments(encoding, args)
|
||||||
result = f(py_self, *args)
|
result = f(py_self, *args)
|
||||||
if isinstance(result, ObjCClass):
|
if isinstance(result, ObjCClass):
|
||||||
@ -1246,17 +1284,15 @@ class DeallocationObserver_Implementation:
|
|||||||
DeallocationObserver.register()
|
DeallocationObserver.register()
|
||||||
|
|
||||||
@DeallocationObserver.rawmethod('@@')
|
@DeallocationObserver.rawmethod('@@')
|
||||||
def initWithObject_(self, cmd, anObject):
|
def initWithObject_(self, cmd, objc_ptr):
|
||||||
self = send_super(self, 'init')
|
self = send_super(self, 'init')
|
||||||
self = self.value
|
self = self.value
|
||||||
set_instance_variable(self, 'observed_object', anObject, c_void_p)
|
set_instance_variable(self, 'observed_object', objc_ptr, c_void_p)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@DeallocationObserver.rawmethod('v')
|
@DeallocationObserver.rawmethod('v')
|
||||||
def dealloc(self, cmd):
|
def dealloc(self, cmd):
|
||||||
anObject = get_instance_variable(self, 'observed_object', c_void_p)
|
_obj_observer_dealloc(self, 'dealloc')
|
||||||
ObjCInstance._cached_objects.pop(anObject, None)
|
|
||||||
send_super(self, 'dealloc')
|
|
||||||
|
|
||||||
@DeallocationObserver.rawmethod('v')
|
@DeallocationObserver.rawmethod('v')
|
||||||
def finalize(self, cmd):
|
def finalize(self, cmd):
|
||||||
@ -1264,6 +1300,40 @@ class DeallocationObserver_Implementation:
|
|||||||
# (which would have to be explicitly started with
|
# (which would have to be explicitly started with
|
||||||
# objc_startCollectorThread(), so probably not too much reason
|
# objc_startCollectorThread(), so probably not too much reason
|
||||||
# to have this here, but I guess it can't hurt.)
|
# to have this here, but I guess it can't hurt.)
|
||||||
anObject = get_instance_variable(self, 'observed_object', c_void_p)
|
_obj_observer_dealloc(self, 'finalize')
|
||||||
ObjCInstance._cached_objects.pop(anObject, None)
|
|
||||||
send_super(self, 'finalize')
|
|
||||||
|
def _obj_observer_dealloc(objc_obs, selector_name):
|
||||||
|
"""Removes any cached ObjCInstances in Python to prevent memory leaks.
|
||||||
|
Manually break association as it's not implicitly mentioned that dealloc would break an association,
|
||||||
|
although we do not use the object after.
|
||||||
|
"""
|
||||||
|
objc_ptr = get_instance_variable(objc_obs, 'observed_object', c_void_p)
|
||||||
|
if objc_ptr:
|
||||||
|
objc.objc_setAssociatedObject(objc_ptr, objc_obs, None, OBJC_ASSOCIATION_ASSIGN)
|
||||||
|
objc_i = ObjCInstance._cached_objects.pop(objc_ptr, None)
|
||||||
|
if objc_i:
|
||||||
|
_clear_arp_objects(objc_i)
|
||||||
|
|
||||||
|
send_super(objc_obs, selector_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_arp_objects(objc_i: ObjCInstance):
|
||||||
|
"""Cleanup any ObjCInstance's created during an AutoreleasePool creation.
|
||||||
|
See discussion and investigation thanks to mrJean with leaks regarding pools:
|
||||||
|
https://github.com/mrJean1/PyCocoa/issues/6
|
||||||
|
It was determined that objects in an AutoreleasePool are not guaranteed to call a dealloc, creating memory leaks.
|
||||||
|
The DeallocObserver relies on this to free memory in the ObjCInstance._cached_objects.
|
||||||
|
Solution is as follows:
|
||||||
|
1) Do not observe any ObjCInstance's with DeallocObserver when non-global autorelease pool is in scope.
|
||||||
|
2) Some objects such as ObjCSubclass's must be retained.
|
||||||
|
3) When a pool is drained and dealloc'd, clear all ObjCInstances in that pool that are not retained.
|
||||||
|
"""
|
||||||
|
if objc_i.objc_class.name == b"NSAutoreleasePool":
|
||||||
|
pool_id = objc_i.pool
|
||||||
|
for cobjc_ptr in list(ObjCInstance._cached_objects.keys()):
|
||||||
|
cobjc_i = ObjCInstance._cached_objects[cobjc_ptr]
|
||||||
|
if cobjc_i.retained is False and cobjc_i.pool == pool_id:
|
||||||
|
del ObjCInstance._cached_objects[cobjc_ptr]
|
||||||
|
|
||||||
|
_arp_manager.delete(objc_i)
|
||||||
|
@ -120,14 +120,14 @@ keymap = {
|
|||||||
VK_F14: key.F14,
|
VK_F14: key.F14,
|
||||||
VK_F15: key.F15,
|
VK_F15: key.F15,
|
||||||
VK_F16: key.F16,
|
VK_F16: key.F16,
|
||||||
# VK_F17: ,
|
VK_F17: key.F17,
|
||||||
# VK_F18: ,
|
VK_F18: key.F18,
|
||||||
# VK_F19: ,
|
VK_F19: key.F19,
|
||||||
# VK_F20: ,
|
VK_F20: key.F20,
|
||||||
# VK_F21: ,
|
VK_F21: key.F21,
|
||||||
# VK_F22: ,
|
VK_F22: key.F22,
|
||||||
# VK_F23: ,
|
VK_F23: key.F23,
|
||||||
# VK_F24: ,
|
VK_F24: key.F24,
|
||||||
VK_NUMLOCK: key.NUMLOCK,
|
VK_NUMLOCK: key.NUMLOCK,
|
||||||
VK_SCROLL: key.SCROLLLOCK,
|
VK_SCROLL: key.SCROLLLOCK,
|
||||||
VK_LSHIFT: key.LSHIFT,
|
VK_LSHIFT: key.LSHIFT,
|
||||||
|
@ -69,12 +69,14 @@ import sys
|
|||||||
|
|
||||||
import pyglet
|
import pyglet
|
||||||
|
|
||||||
from pyglet.gl import *
|
|
||||||
from pyglet import clock
|
from pyglet import clock
|
||||||
from pyglet import event
|
|
||||||
from pyglet import graphics
|
from pyglet import graphics
|
||||||
from pyglet import image
|
from pyglet import image
|
||||||
|
|
||||||
|
from pyglet.gl import *
|
||||||
|
from pyglet.animation import AnimationController, Animation
|
||||||
|
|
||||||
|
|
||||||
_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
|
||||||
|
|
||||||
|
|
||||||
@ -233,16 +235,13 @@ class SpriteGroup(graphics.Group):
|
|||||||
self.blend_src, self.blend_dest))
|
self.blend_src, self.blend_dest))
|
||||||
|
|
||||||
|
|
||||||
class Sprite(event.EventDispatcher):
|
class Sprite(AnimationController):
|
||||||
"""Instance of an on-screen image.
|
"""Instance of an on-screen image.
|
||||||
|
|
||||||
See the module documentation for usage.
|
See the module documentation for usage.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_batch = None
|
_batch = None
|
||||||
_animation = None
|
|
||||||
_frame_index = 0
|
|
||||||
_paused = False
|
|
||||||
_rotation = 0
|
_rotation = 0
|
||||||
_opacity = 255
|
_opacity = 255
|
||||||
_rgb = (255, 255, 255)
|
_rgb = (255, 255, 255)
|
||||||
@ -263,7 +262,7 @@ class Sprite(event.EventDispatcher):
|
|||||||
"""Create a sprite.
|
"""Create a sprite.
|
||||||
|
|
||||||
:Parameters:
|
:Parameters:
|
||||||
`img` : `~pyglet.image.AbstractImage` or `~pyglet.image.Animation`
|
`img` : `~pyglet.image.AbstractImage` or `~pyglet.animation.Animation`
|
||||||
Image or animation to display.
|
Image or animation to display.
|
||||||
`x` : int
|
`x` : int
|
||||||
X coordinate of the sprite.
|
X coordinate of the sprite.
|
||||||
@ -290,9 +289,9 @@ class Sprite(event.EventDispatcher):
|
|||||||
self._z = z
|
self._z = z
|
||||||
self._img = img
|
self._img = img
|
||||||
|
|
||||||
if isinstance(img, image.Animation):
|
if isinstance(img, Animation):
|
||||||
self._animation = img
|
self._animation = img
|
||||||
self._texture = img.frames[0].image.get_texture()
|
self._texture = img.frames[0].data.get_texture()
|
||||||
self._next_dt = img.frames[0].duration
|
self._next_dt = img.frames[0].duration
|
||||||
if self._next_dt:
|
if self._next_dt:
|
||||||
clock.schedule_once(self._animate, self._next_dt)
|
clock.schedule_once(self._animate, self._next_dt)
|
||||||
@ -345,7 +344,7 @@ class Sprite(event.EventDispatcher):
|
|||||||
return # Deleted in event handler.
|
return # Deleted in event handler.
|
||||||
|
|
||||||
frame = self._animation.frames[self._frame_index]
|
frame = self._animation.frames[self._frame_index]
|
||||||
self._set_texture(frame.image.get_texture())
|
self._set_texture(frame.data.get_texture())
|
||||||
|
|
||||||
if frame.duration is not None:
|
if frame.duration is not None:
|
||||||
duration = frame.duration - (self._next_dt - dt)
|
duration = frame.duration - (self._next_dt - dt)
|
||||||
@ -407,7 +406,7 @@ class Sprite(event.EventDispatcher):
|
|||||||
"""Image or animation to display.
|
"""Image or animation to display.
|
||||||
|
|
||||||
:type: :py:class:`~pyglet.image.AbstractImage` or
|
:type: :py:class:`~pyglet.image.AbstractImage` or
|
||||||
:py:class:`~pyglet.image.Animation`
|
:py:class:`~pyglet.animation.Animation`
|
||||||
"""
|
"""
|
||||||
if self._animation:
|
if self._animation:
|
||||||
return self._animation
|
return self._animation
|
||||||
@ -419,7 +418,7 @@ class Sprite(event.EventDispatcher):
|
|||||||
clock.unschedule(self._animate)
|
clock.unschedule(self._animate)
|
||||||
self._animation = None
|
self._animation = None
|
||||||
|
|
||||||
if isinstance(img, image.Animation):
|
if isinstance(img, Animation):
|
||||||
self._animation = img
|
self._animation = img
|
||||||
self._frame_index = 0
|
self._frame_index = 0
|
||||||
self._set_texture(img.frames[0].image.get_texture())
|
self._set_texture(img.frames[0].image.get_texture())
|
||||||
@ -730,51 +729,6 @@ class Sprite(event.EventDispatcher):
|
|||||||
self._visible = visible
|
self._visible = visible
|
||||||
self._update_position()
|
self._update_position()
|
||||||
|
|
||||||
@property
|
|
||||||
def paused(self):
|
|
||||||
"""Pause/resume the Sprite's Animation
|
|
||||||
|
|
||||||
If `Sprite.image` is an Animation, you can pause or resume
|
|
||||||
the animation by setting this property to True or False.
|
|
||||||
If not an Animation, this has no effect.
|
|
||||||
|
|
||||||
:type: bool
|
|
||||||
"""
|
|
||||||
return self._paused
|
|
||||||
|
|
||||||
@paused.setter
|
|
||||||
def paused(self, pause):
|
|
||||||
if not hasattr(self, '_animation') or pause == self._paused:
|
|
||||||
return
|
|
||||||
if pause is True:
|
|
||||||
clock.unschedule(self._animate)
|
|
||||||
else:
|
|
||||||
frame = self._animation.frames[self._frame_index]
|
|
||||||
self._next_dt = frame.duration
|
|
||||||
if self._next_dt:
|
|
||||||
clock.schedule_once(self._animate, self._next_dt)
|
|
||||||
self._paused = pause
|
|
||||||
|
|
||||||
@property
|
|
||||||
def frame_index(self):
|
|
||||||
"""The current Animation frame.
|
|
||||||
|
|
||||||
If the `Sprite.image` is an `Animation`,
|
|
||||||
you can query or set the current frame.
|
|
||||||
If not an Animation, this will always
|
|
||||||
be 0.
|
|
||||||
|
|
||||||
:type: int
|
|
||||||
"""
|
|
||||||
return self._frame_index
|
|
||||||
|
|
||||||
@frame_index.setter
|
|
||||||
def frame_index(self, index):
|
|
||||||
# Bound to available number of frames
|
|
||||||
if self._animation is None:
|
|
||||||
return
|
|
||||||
self._frame_index = max(0, min(index, len(self._animation.frames)-1))
|
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
"""Draw the sprite at its current position.
|
"""Draw the sprite at its current position.
|
||||||
|
|
||||||
@ -797,9 +751,6 @@ class Sprite(event.EventDispatcher):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
Sprite.register_event_type('on_animation_end')
|
|
||||||
|
|
||||||
|
|
||||||
class AdvancedSprite(pyglet.sprite.Sprite):
|
class AdvancedSprite(pyglet.sprite.Sprite):
|
||||||
"""Is a sprite that lets you change the shader program during initialization and after
|
"""Is a sprite that lets you change the shader program during initialization and after
|
||||||
For advanced users who understand shaders."""
|
For advanced users who understand shaders."""
|
||||||
@ -837,7 +788,3 @@ class AdvancedSprite(pyglet.sprite.Sprite):
|
|||||||
self._group)
|
self._group)
|
||||||
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, self._batch)
|
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, self._batch)
|
||||||
self._program = program
|
self._program = program
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1011,7 +1011,7 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
|
|||||||
|
|
||||||
Once set, the user will not be able to resize the window smaller
|
Once set, the user will not be able to resize the window smaller
|
||||||
than the given dimensions. There is no way to remove the
|
than the given dimensions. There is no way to remove the
|
||||||
minimum size constraint on a window (but you could set it to 0,0).
|
minimum size constraint on a window (but you could set it to 1, 1).
|
||||||
|
|
||||||
The behaviour is undefined if the minimum size is set larger than
|
The behaviour is undefined if the minimum size is set larger than
|
||||||
the current size of the window.
|
the current size of the window.
|
||||||
|
@ -8,7 +8,7 @@ from pyglet.event import EventDispatcher
|
|||||||
|
|
||||||
from pyglet.canvas.cocoa import CocoaCanvas
|
from pyglet.canvas.cocoa import CocoaCanvas
|
||||||
|
|
||||||
from pyglet.libs.darwin import cocoapy, CGPoint
|
from pyglet.libs.darwin import cocoapy, CGPoint, AutoReleasePool
|
||||||
|
|
||||||
from .systemcursor import SystemCursor
|
from .systemcursor import SystemCursor
|
||||||
from .pyglet_delegate import PygletDelegate
|
from .pyglet_delegate import PygletDelegate
|
||||||
@ -17,7 +17,6 @@ from .pyglet_view import PygletView
|
|||||||
|
|
||||||
NSApplication = cocoapy.ObjCClass('NSApplication')
|
NSApplication = cocoapy.ObjCClass('NSApplication')
|
||||||
NSCursor = cocoapy.ObjCClass('NSCursor')
|
NSCursor = cocoapy.ObjCClass('NSCursor')
|
||||||
NSAutoreleasePool = cocoapy.ObjCClass('NSAutoreleasePool')
|
|
||||||
NSColor = cocoapy.ObjCClass('NSColor')
|
NSColor = cocoapy.ObjCClass('NSColor')
|
||||||
NSEvent = cocoapy.ObjCClass('NSEvent')
|
NSEvent = cocoapy.ObjCClass('NSEvent')
|
||||||
NSArray = cocoapy.ObjCClass('NSArray')
|
NSArray = cocoapy.ObjCClass('NSArray')
|
||||||
@ -42,6 +41,13 @@ class CocoaMouseCursor(MouseCursor):
|
|||||||
|
|
||||||
class CocoaWindow(BaseWindow):
|
class CocoaWindow(BaseWindow):
|
||||||
|
|
||||||
|
def __init__(self, width=None, height=None, caption=None, resizable=False, style=BaseWindow.WINDOW_STYLE_DEFAULT,
|
||||||
|
fullscreen=False, visible=True, vsync=True, file_drops=False, display=None, screen=None, config=None,
|
||||||
|
context=None, mode=None):
|
||||||
|
with AutoReleasePool():
|
||||||
|
super().__init__(width, height, caption, resizable, style, fullscreen, visible, vsync, file_drops, display,
|
||||||
|
screen, config, context, mode)
|
||||||
|
|
||||||
# NSWindow instance.
|
# NSWindow instance.
|
||||||
_nswindow = None
|
_nswindow = None
|
||||||
|
|
||||||
@ -79,9 +85,7 @@ class CocoaWindow(BaseWindow):
|
|||||||
self._create()
|
self._create()
|
||||||
|
|
||||||
def _create(self):
|
def _create(self):
|
||||||
# Create a temporary autorelease pool for this method.
|
with AutoReleasePool():
|
||||||
pool = NSAutoreleasePool.alloc().init()
|
|
||||||
|
|
||||||
if self._nswindow:
|
if self._nswindow:
|
||||||
# The window is about the be recreated so destroy everything
|
# The window is about the be recreated so destroy everything
|
||||||
# associated with the old window, then destroy the window itself.
|
# associated with the old window, then destroy the window itself.
|
||||||
@ -180,8 +184,6 @@ class CocoaWindow(BaseWindow):
|
|||||||
self.set_vsync(self._vsync)
|
self.set_vsync(self._vsync)
|
||||||
self.set_visible(self._visible)
|
self.set_visible(self._visible)
|
||||||
|
|
||||||
pool.drain()
|
|
||||||
|
|
||||||
def _set_nice_window_location(self):
|
def _set_nice_window_location(self):
|
||||||
# Construct a list of all visible windows that aren't us.
|
# Construct a list of all visible windows that aren't us.
|
||||||
visible_windows = [win for win in pyglet.app.windows if
|
visible_windows = [win for win in pyglet.app.windows if
|
||||||
@ -208,9 +210,7 @@ class CocoaWindow(BaseWindow):
|
|||||||
if self._was_closed:
|
if self._was_closed:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create a temporary autorelease pool for this method.
|
with AutoReleasePool():
|
||||||
pool = NSAutoreleasePool.new()
|
|
||||||
|
|
||||||
# Restore cursor visibility
|
# Restore cursor visibility
|
||||||
self.set_mouse_platform_visible(True)
|
self.set_mouse_platform_visible(True)
|
||||||
self.set_exclusive_mouse(False)
|
self.set_exclusive_mouse(False)
|
||||||
@ -243,7 +243,6 @@ class CocoaWindow(BaseWindow):
|
|||||||
super(CocoaWindow, self).close()
|
super(CocoaWindow, self).close()
|
||||||
|
|
||||||
self._was_closed = True
|
self._was_closed = True
|
||||||
pool.drain()
|
|
||||||
|
|
||||||
def switch_to(self):
|
def switch_to(self):
|
||||||
if self.context:
|
if self.context:
|
||||||
@ -261,7 +260,7 @@ class CocoaWindow(BaseWindow):
|
|||||||
event = True
|
event = True
|
||||||
|
|
||||||
# Dequeue and process all of the pending Cocoa events.
|
# Dequeue and process all of the pending Cocoa events.
|
||||||
pool = NSAutoreleasePool.new()
|
with AutoReleasePool():
|
||||||
NSApp = NSApplication.sharedApplication()
|
NSApp = NSApplication.sharedApplication()
|
||||||
while event and self._nswindow and self._context:
|
while event and self._nswindow and self._context:
|
||||||
event = NSApp.nextEventMatchingMask_untilDate_inMode_dequeue_(
|
event = NSApp.nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||||
@ -280,8 +279,6 @@ class CocoaWindow(BaseWindow):
|
|||||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletFlagsChanged:'), None, event)
|
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletFlagsChanged:'), None, event)
|
||||||
NSApp.updateWindows()
|
NSApp.updateWindows()
|
||||||
|
|
||||||
pool.drain()
|
|
||||||
|
|
||||||
self._allow_dispatch_event = False
|
self._allow_dispatch_event = False
|
||||||
|
|
||||||
def dispatch_pending_events(self):
|
def dispatch_pending_events(self):
|
||||||
|
@ -293,7 +293,10 @@ F17 = 0xffce
|
|||||||
F18 = 0xffcf
|
F18 = 0xffcf
|
||||||
F19 = 0xffd0
|
F19 = 0xffd0
|
||||||
F20 = 0xffd1
|
F20 = 0xffd1
|
||||||
|
F21 = 0xffd2
|
||||||
|
F22 = 0xffd3
|
||||||
|
F23 = 0xffd4
|
||||||
|
F24 = 0xffd5
|
||||||
# Modifiers
|
# Modifiers
|
||||||
LSHIFT = 0xffe1
|
LSHIFT = 0xffe1
|
||||||
RSHIFT = 0xffe2
|
RSHIFT = 0xffe2
|
||||||
|
Loading…
Reference in New Issue
Block a user