这是什么? 新的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
|
||||
# type checking or code inspection.
|
||||
if TYPE_CHECKING:
|
||||
from . import animation
|
||||
from . import app
|
||||
from . import canvas
|
||||
from . import clock
|
||||
@ -349,6 +350,7 @@ if TYPE_CHECKING:
|
||||
from . import text
|
||||
from . import window
|
||||
else:
|
||||
animation = _ModuleProxy('animation')
|
||||
app = _ModuleProxy('app')
|
||||
canvas = _ModuleProxy('canvas')
|
||||
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.libs.darwin import cocoapy
|
||||
from pyglet.libs.darwin import cocoapy, AutoReleasePool
|
||||
|
||||
NSApplication = cocoapy.ObjCClass('NSApplication')
|
||||
NSMenu = cocoapy.ObjCClass('NSMenu')
|
||||
NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
|
||||
NSAutoreleasePool = cocoapy.ObjCClass('NSAutoreleasePool')
|
||||
NSDate = cocoapy.ObjCClass('NSDate')
|
||||
NSDate.dateWithTimeIntervalSinceNow_.no_cached_return()
|
||||
NSDate.distantFuture.no_cached_return()
|
||||
|
||||
NSEvent = cocoapy.ObjCClass('NSEvent')
|
||||
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):
|
||||
@ -33,8 +21,6 @@ def add_menu_item(menu, title, action, key):
|
||||
menu.addItem_(menuItem)
|
||||
|
||||
# cleanup
|
||||
title.release()
|
||||
key.release()
|
||||
menuItem.release()
|
||||
|
||||
|
||||
|
@ -5,53 +5,6 @@ Shaders and Buffers. It also provides classes for highly performant batched
|
||||
rendering and grouping.
|
||||
|
||||
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
|
||||
@ -255,15 +208,31 @@ def get_default_shader():
|
||||
|
||||
|
||||
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
|
||||
`add` and `add_indexed` methods. An optional group can be specified along
|
||||
with the vertex list, which gives the OpenGL state required for its rendering.
|
||||
Vertex lists with shared mode and group are allocated into adjacent areas of
|
||||
memory and sent to the graphics card in a single operation.
|
||||
Many drawable pyglet objects accept an optional `Batch` argument in their
|
||||
constructors. By giving a `Batch` to multiple objects, you can tell pyglet
|
||||
that you expect to draw all of these objects at once, so it can optimise its
|
||||
use of OpenGL. Hence, drawing a `Batch` is often much faster than drawing
|
||||
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):
|
||||
@ -325,6 +294,7 @@ class Batch:
|
||||
vertex_list.migrate(domain)
|
||||
|
||||
def get_domain(self, indexed, mode, group, program, attributes):
|
||||
"""Get, or create, the vertex domain corresponding to the given arguments."""
|
||||
if group is None:
|
||||
group = ShaderGroup(program=program)
|
||||
|
||||
@ -496,27 +466,43 @@ class Batch:
|
||||
class Group:
|
||||
"""Group of common OpenGL state.
|
||||
|
||||
Before a VertexList is rendered, its Group's OpenGL state is set.
|
||||
This includes binding textures, shaders, or setting any other parameters.
|
||||
`Group` provides extra control over how drawables are handled within a
|
||||
`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.
|
||||
It also sorts the groups before drawing.
|
||||
|
||||
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:
|
||||
`order` : int
|
||||
Set the order to render above or below other Groups.
|
||||
Lower orders are drawn first.
|
||||
`parent` : `~pyglet.graphics.Group`
|
||||
Group to contain this Group; its state will be set before this
|
||||
Group's state.
|
||||
|
||||
:Variables:
|
||||
`visible` : bool
|
||||
Determines whether this Group is visible in any of the Batches
|
||||
it is assigned to. If ``False``, objects in this Group will not
|
||||
be rendered.
|
||||
`batches` : list
|
||||
Read Only. A list of which Batches this Group is a part of.
|
||||
"""
|
||||
def __init__(self, order=0, parent=None):
|
||||
"""Create a Group.
|
||||
|
||||
:Parameters:
|
||||
`order` : int
|
||||
Set the order to render above or below other Groups.
|
||||
`parent` : `~pyglet.graphics.Group`
|
||||
Group to contain this Group; its state will be set before this
|
||||
Group's state.
|
||||
|
||||
:Ivariables:
|
||||
`visible` : bool
|
||||
Determines whether this Group is visible in any of the Batches
|
||||
it is assigned to. If False, objects in this Group will not
|
||||
be rendered.
|
||||
`batches` : list
|
||||
Read Only. A list of which Batches this Group is a part of.
|
||||
"""
|
||||
self._order = order
|
||||
self.parent = parent
|
||||
self._visible = True
|
||||
|
@ -645,19 +645,16 @@ class ShaderSource:
|
||||
|
||||
|
||||
class Shader:
|
||||
"""OpenGL Shader object"""
|
||||
"""OpenGL shader.
|
||||
|
||||
Shader objects are compiled on instantiation.
|
||||
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):
|
||||
"""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.
|
||||
You can reuse a Shader object in multiple `ShaderProgram`s.
|
||||
"""
|
||||
self._id = None
|
||||
self.type = shader_type
|
||||
|
||||
@ -735,12 +732,11 @@ class Shader:
|
||||
|
||||
|
||||
class ShaderProgram:
|
||||
"""OpenGL Shader Program"""
|
||||
"""OpenGL shader program."""
|
||||
|
||||
__slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__'
|
||||
|
||||
def __init__(self, *shaders: Shader):
|
||||
"""Create an OpenGL ShaderProgram from one or more Shader objects."""
|
||||
assert shaders, "At least one Shader object is required."
|
||||
self._id = _link_program(*shaders)
|
||||
self._context = pyglet.gl.current_context
|
||||
@ -825,6 +821,7 @@ class ShaderProgram:
|
||||
`mode` : int
|
||||
OpenGL drawing mode enumeration; for example, one of
|
||||
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
||||
This determines how the list is drawn in the given batch.
|
||||
`batch` : `~pyglet.graphics.Batch`
|
||||
Batch to add the VertexList to, or ``None`` if a Batch will not be used.
|
||||
Using a Batch is strongly recommended.
|
||||
@ -867,6 +864,7 @@ class ShaderProgram:
|
||||
`mode` : int
|
||||
OpenGL drawing mode enumeration; for example, one of
|
||||
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
|
||||
This determines how the list is drawn in the given batch.
|
||||
`indices` : sequence of int
|
||||
Sequence of integers giving indices into the vertex list.
|
||||
`batch` : `~pyglet.graphics.Batch`
|
||||
|
@ -97,19 +97,17 @@ import re
|
||||
import weakref
|
||||
|
||||
from ctypes import *
|
||||
from io import open, BytesIO
|
||||
from io import open
|
||||
|
||||
import pyglet
|
||||
|
||||
from pyglet.gl import *
|
||||
from pyglet.gl import gl_info
|
||||
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 add_default_codecs as _add_default_codecs
|
||||
from .codecs import ImageEncodeException, ImageDecodeException
|
||||
|
||||
from .animation import Animation, AnimationFrame
|
||||
from .buffer import *
|
||||
from . import atlas
|
||||
|
||||
@ -449,7 +447,7 @@ class AbstractImageSequence:
|
||||
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
return Animation.from_image_sequence(self, period, loop)
|
||||
return Animation.from_sequence(self, period, loop)
|
||||
|
||||
def __getitem__(self, slice):
|
||||
"""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.codecs import *
|
||||
from pyglet.image.codecs import gif
|
||||
from pyglet.animation import AnimationFrame
|
||||
|
||||
import pyglet.lib
|
||||
import pyglet.window
|
||||
|
@ -35,3 +35,15 @@ from .runtime import ObjCClass, ObjCInstance, ObjCSubclass
|
||||
|
||||
from .cocoatypes 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
|
||||
|
||||
def CFSTR(string):
|
||||
return ObjCInstance(c_void_p(cf.CFStringCreateWithCString(
|
||||
None, string.encode('utf8'), kCFStringEncodingUTF8)))
|
||||
return cf.CFStringCreateWithCString(None, string.encode('utf8'), kCFStringEncodingUTF8)
|
||||
|
||||
# Other possible names for this method:
|
||||
# at, ampersat, arobe, apenstaartje (little monkey tail), strudel,
|
||||
@ -57,7 +56,7 @@ def CFSTR(string):
|
||||
# kukac (caterpillar).
|
||||
def get_NSString(string):
|
||||
"""Autoreleased version of CFSTR"""
|
||||
return CFSTR(string).autorelease()
|
||||
return ObjCInstance(c_void_p(CFSTR(string))).autorelease()
|
||||
|
||||
def cfstring_to_string(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.argtypes = [c_char_p]
|
||||
|
||||
|
||||
######################################################################
|
||||
# Constants
|
||||
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
|
||||
# and argtypes should be a list of ctypes types for
|
||||
# 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):
|
||||
if isinstance(receiver, str):
|
||||
receiver = get_class(receiver)
|
||||
@ -624,11 +625,11 @@ def cfunctype_for_encoding(encoding):
|
||||
return cfunctype_table[encoding]
|
||||
|
||||
# Otherwise, create a new CFUNCTYPE for the encoding.
|
||||
typecodes = {b'c': c_char, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong,
|
||||
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong,
|
||||
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'*': c_char_p,
|
||||
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, NSPointEncoding: NSPoint,
|
||||
NSSizeEncoding: NSSize, NSRectEncoding: NSRect, NSRangeEncoding: NSRange,
|
||||
typecodes = {b'c': c_char, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong,
|
||||
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong,
|
||||
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'*': c_char_p,
|
||||
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, NSPointEncoding: NSPoint,
|
||||
NSSizeEncoding: NSSize, NSRectEncoding: NSRect, NSRangeEncoding: NSRange,
|
||||
PyObjectEncoding: py_object}
|
||||
argtypes = []
|
||||
for code in parse_type_encoding(encoding):
|
||||
@ -704,12 +705,12 @@ class ObjCMethod:
|
||||
# Note, need to map 'c' to c_byte rather than c_char, because otherwise
|
||||
# ctypes converts the value into a one-character string which is generally
|
||||
# not what we want at all, especially when the 'c' represents a bool var.
|
||||
typecodes = {b'c': c_byte, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong,
|
||||
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong,
|
||||
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'Vv': None, b'*': c_char_p,
|
||||
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, b'^v': c_void_p, b'?': c_void_p,
|
||||
NSPointEncoding: NSPoint, NSSizeEncoding: NSSize, NSRectEncoding: NSRect,
|
||||
NSRangeEncoding: NSRange,
|
||||
typecodes = {b'c': c_byte, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong,
|
||||
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong,
|
||||
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'Vv': None, b'*': c_char_p,
|
||||
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, b'^v': c_void_p, b'?': c_void_p,
|
||||
NSPointEncoding: NSPoint, NSSizeEncoding: NSSize, NSRectEncoding: NSRect,
|
||||
NSRangeEncoding: NSRange,
|
||||
PyObjectEncoding: py_object}
|
||||
|
||||
cfunctype_table = {}
|
||||
@ -717,7 +718,6 @@ class ObjCMethod:
|
||||
def __init__(self, method):
|
||||
"""Initialize with an Objective-C Method pointer. We then determine
|
||||
the return type and argument type information of the method."""
|
||||
self.cache = True
|
||||
self.selector = c_void_p(objc.method_getName(method))
|
||||
self.name = objc.sel_getName(self.selector)
|
||||
self.pyname = self.name.replace(b':', b'_')
|
||||
@ -734,7 +734,7 @@ class ObjCMethod:
|
||||
try:
|
||||
self.argtypes = [self.ctype_for_encoding(t) for t in self.argument_types]
|
||||
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
|
||||
# Get types for the return type.
|
||||
try:
|
||||
@ -745,7 +745,7 @@ class ObjCMethod:
|
||||
else:
|
||||
self.restype = self.ctype_for_encoding(self.return_type)
|
||||
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.func = None
|
||||
|
||||
@ -803,7 +803,7 @@ class ObjCMethod:
|
||||
result = f(objc_id, self.selector, *args)
|
||||
# Convert result to python type if it is a instance or class pointer.
|
||||
if self.restype == ObjCInstance:
|
||||
result = ObjCInstance(result, self.cache)
|
||||
result = ObjCInstance(result)
|
||||
elif self.restype == ObjCClass:
|
||||
result = ObjCClass(result)
|
||||
return result
|
||||
@ -826,13 +826,6 @@ class ObjCBoundMethod:
|
||||
self.method = method
|
||||
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):
|
||||
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:
|
||||
"""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 = {}
|
||||
|
||||
@ -1004,17 +1038,16 @@ class ObjCInstance:
|
||||
if cache:
|
||||
cls._cached_objects[object_ptr.value] = objc_instance
|
||||
|
||||
# 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(send_message('DeallocationObserver', 'alloc'), 'initWithObject:', objc_instance)
|
||||
objc.objc_setAssociatedObject(objc_instance, 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')
|
||||
# Creation of NSAutoreleasePool instance does not technically mean it was allocated and initialized, but
|
||||
# it's standard practice, so this should not be an issue.
|
||||
if objc_instance.objc_class.name == b"NSAutoreleasePool":
|
||||
_arp_manager.create(objc_instance)
|
||||
objc_instance.pool = _arp_manager.current
|
||||
_set_dealloc_observer(object_ptr)
|
||||
elif _arp_manager.current:
|
||||
objc_instance.pool = _arp_manager.current
|
||||
else:
|
||||
_set_dealloc_observer(object_ptr)
|
||||
|
||||
return objc_instance
|
||||
|
||||
@ -1048,12 +1081,16 @@ class ObjCInstance:
|
||||
# Otherwise raise an exception.
|
||||
raise AttributeError('ObjCInstance %s has no attribute %s' % (self.objc_class.name, name))
|
||||
|
||||
|
||||
def get_cached_instances():
|
||||
"""For debug purposes, return a list of instance names.
|
||||
Useful for debugging if an object is leaking."""
|
||||
return [obj.objc_class.name for obj in ObjCInstance._cached_objects.values()]
|
||||
|
||||
|
||||
######################################################################
|
||||
|
||||
|
||||
def convert_method_arguments(encoding, args):
|
||||
"""Used by ObjCSubclass to convert Objective-C method arguments to
|
||||
Python values before passing them on to the Python-defined method."""
|
||||
@ -1183,8 +1220,9 @@ class ObjCSubclass:
|
||||
|
||||
def decorator(f):
|
||||
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.retained = True
|
||||
args = convert_method_arguments(encoding, args)
|
||||
result = f(py_self, *args)
|
||||
if isinstance(result, ObjCClass):
|
||||
@ -1246,17 +1284,15 @@ class DeallocationObserver_Implementation:
|
||||
DeallocationObserver.register()
|
||||
|
||||
@DeallocationObserver.rawmethod('@@')
|
||||
def initWithObject_(self, cmd, anObject):
|
||||
def initWithObject_(self, cmd, objc_ptr):
|
||||
self = send_super(self, 'init')
|
||||
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
|
||||
|
||||
@DeallocationObserver.rawmethod('v')
|
||||
def dealloc(self, cmd):
|
||||
anObject = get_instance_variable(self, 'observed_object', c_void_p)
|
||||
ObjCInstance._cached_objects.pop(anObject, None)
|
||||
send_super(self, 'dealloc')
|
||||
_obj_observer_dealloc(self, 'dealloc')
|
||||
|
||||
@DeallocationObserver.rawmethod('v')
|
||||
def finalize(self, cmd):
|
||||
@ -1264,6 +1300,40 @@ class DeallocationObserver_Implementation:
|
||||
# (which would have to be explicitly started with
|
||||
# objc_startCollectorThread(), so probably not too much reason
|
||||
# to have this here, but I guess it can't hurt.)
|
||||
anObject = get_instance_variable(self, 'observed_object', c_void_p)
|
||||
ObjCInstance._cached_objects.pop(anObject, None)
|
||||
send_super(self, 'finalize')
|
||||
_obj_observer_dealloc(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_F15: key.F15,
|
||||
VK_F16: key.F16,
|
||||
# VK_F17: ,
|
||||
# VK_F18: ,
|
||||
# VK_F19: ,
|
||||
# VK_F20: ,
|
||||
# VK_F21: ,
|
||||
# VK_F22: ,
|
||||
# VK_F23: ,
|
||||
# VK_F24: ,
|
||||
VK_F17: key.F17,
|
||||
VK_F18: key.F18,
|
||||
VK_F19: key.F19,
|
||||
VK_F20: key.F20,
|
||||
VK_F21: key.F21,
|
||||
VK_F22: key.F22,
|
||||
VK_F23: key.F23,
|
||||
VK_F24: key.F24,
|
||||
VK_NUMLOCK: key.NUMLOCK,
|
||||
VK_SCROLL: key.SCROLLLOCK,
|
||||
VK_LSHIFT: key.LSHIFT,
|
||||
|
@ -69,12 +69,14 @@ import sys
|
||||
|
||||
import pyglet
|
||||
|
||||
from pyglet.gl import *
|
||||
from pyglet import clock
|
||||
from pyglet import event
|
||||
from pyglet import graphics
|
||||
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
|
||||
|
||||
|
||||
@ -233,16 +235,13 @@ class SpriteGroup(graphics.Group):
|
||||
self.blend_src, self.blend_dest))
|
||||
|
||||
|
||||
class Sprite(event.EventDispatcher):
|
||||
class Sprite(AnimationController):
|
||||
"""Instance of an on-screen image.
|
||||
|
||||
See the module documentation for usage.
|
||||
"""
|
||||
|
||||
_batch = None
|
||||
_animation = None
|
||||
_frame_index = 0
|
||||
_paused = False
|
||||
_rotation = 0
|
||||
_opacity = 255
|
||||
_rgb = (255, 255, 255)
|
||||
@ -263,7 +262,7 @@ class Sprite(event.EventDispatcher):
|
||||
"""Create a sprite.
|
||||
|
||||
:Parameters:
|
||||
`img` : `~pyglet.image.AbstractImage` or `~pyglet.image.Animation`
|
||||
`img` : `~pyglet.image.AbstractImage` or `~pyglet.animation.Animation`
|
||||
Image or animation to display.
|
||||
`x` : int
|
||||
X coordinate of the sprite.
|
||||
@ -290,9 +289,9 @@ class Sprite(event.EventDispatcher):
|
||||
self._z = z
|
||||
self._img = img
|
||||
|
||||
if isinstance(img, image.Animation):
|
||||
if isinstance(img, Animation):
|
||||
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
|
||||
if self._next_dt:
|
||||
clock.schedule_once(self._animate, self._next_dt)
|
||||
@ -345,7 +344,7 @@ class Sprite(event.EventDispatcher):
|
||||
return # Deleted in event handler.
|
||||
|
||||
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:
|
||||
duration = frame.duration - (self._next_dt - dt)
|
||||
@ -407,7 +406,7 @@ class Sprite(event.EventDispatcher):
|
||||
"""Image or animation to display.
|
||||
|
||||
:type: :py:class:`~pyglet.image.AbstractImage` or
|
||||
:py:class:`~pyglet.image.Animation`
|
||||
:py:class:`~pyglet.animation.Animation`
|
||||
"""
|
||||
if self._animation:
|
||||
return self._animation
|
||||
@ -419,7 +418,7 @@ class Sprite(event.EventDispatcher):
|
||||
clock.unschedule(self._animate)
|
||||
self._animation = None
|
||||
|
||||
if isinstance(img, image.Animation):
|
||||
if isinstance(img, Animation):
|
||||
self._animation = img
|
||||
self._frame_index = 0
|
||||
self._set_texture(img.frames[0].image.get_texture())
|
||||
@ -730,51 +729,6 @@ class Sprite(event.EventDispatcher):
|
||||
self._visible = visible
|
||||
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):
|
||||
"""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):
|
||||
"""Is a sprite that lets you change the shader program during initialization and after
|
||||
For advanced users who understand shaders."""
|
||||
@ -837,7 +788,3 @@ class AdvancedSprite(pyglet.sprite.Sprite):
|
||||
self._group)
|
||||
self._batch.migrate(self._vertex_list, GL_TRIANGLES, self._group, self._batch)
|
||||
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
|
||||
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 current size of the window.
|
||||
|
@ -8,7 +8,7 @@ from pyglet.event import EventDispatcher
|
||||
|
||||
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 .pyglet_delegate import PygletDelegate
|
||||
@ -17,7 +17,6 @@ from .pyglet_view import PygletView
|
||||
|
||||
NSApplication = cocoapy.ObjCClass('NSApplication')
|
||||
NSCursor = cocoapy.ObjCClass('NSCursor')
|
||||
NSAutoreleasePool = cocoapy.ObjCClass('NSAutoreleasePool')
|
||||
NSColor = cocoapy.ObjCClass('NSColor')
|
||||
NSEvent = cocoapy.ObjCClass('NSEvent')
|
||||
NSArray = cocoapy.ObjCClass('NSArray')
|
||||
@ -42,6 +41,13 @@ class CocoaMouseCursor(MouseCursor):
|
||||
|
||||
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 = None
|
||||
|
||||
@ -57,14 +63,14 @@ class CocoaWindow(BaseWindow):
|
||||
|
||||
# NSWindow style masks.
|
||||
_style_masks = {
|
||||
BaseWindow.WINDOW_STYLE_DEFAULT: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask |
|
||||
cocoapy.NSMiniaturizableWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_DIALOG: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_TOOL: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask |
|
||||
cocoapy.NSUtilityWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_DEFAULT: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask |
|
||||
cocoapy.NSMiniaturizableWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_DIALOG: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_TOOL: cocoapy.NSTitledWindowMask |
|
||||
cocoapy.NSClosableWindowMask |
|
||||
cocoapy.NSUtilityWindowMask,
|
||||
BaseWindow.WINDOW_STYLE_BORDERLESS: cocoapy.NSBorderlessWindowMask,
|
||||
}
|
||||
|
||||
@ -79,108 +85,104 @@ class CocoaWindow(BaseWindow):
|
||||
self._create()
|
||||
|
||||
def _create(self):
|
||||
# Create a temporary autorelease pool for this method.
|
||||
pool = NSAutoreleasePool.alloc().init()
|
||||
with AutoReleasePool():
|
||||
if self._nswindow:
|
||||
# The window is about the be recreated so destroy everything
|
||||
# associated with the old window, then destroy the window itself.
|
||||
nsview = self.canvas.nsview
|
||||
self.canvas = None
|
||||
self._nswindow.orderOut_(None)
|
||||
self._nswindow.close()
|
||||
self.context.detach()
|
||||
self._nswindow.release()
|
||||
self._nswindow = None
|
||||
nsview.release()
|
||||
self._delegate.release()
|
||||
self._delegate = None
|
||||
|
||||
if self._nswindow:
|
||||
# The window is about the be recreated so destroy everything
|
||||
# associated with the old window, then destroy the window itself.
|
||||
nsview = self.canvas.nsview
|
||||
self.canvas = None
|
||||
self._nswindow.orderOut_(None)
|
||||
self._nswindow.close()
|
||||
self.context.detach()
|
||||
self._nswindow.release()
|
||||
self._nswindow = None
|
||||
nsview.release()
|
||||
self._delegate.release()
|
||||
self._delegate = None
|
||||
# Determine window parameters.
|
||||
content_rect = cocoapy.NSMakeRect(0, 0, self._width, self._height)
|
||||
WindowClass = PygletWindow
|
||||
if self._fullscreen:
|
||||
style_mask = cocoapy.NSBorderlessWindowMask
|
||||
else:
|
||||
if self._style not in self._style_masks:
|
||||
self._style = self.WINDOW_STYLE_DEFAULT
|
||||
style_mask = self._style_masks[self._style]
|
||||
if self._resizable:
|
||||
style_mask |= cocoapy.NSResizableWindowMask
|
||||
if self._style == BaseWindow.WINDOW_STYLE_TOOL:
|
||||
WindowClass = PygletToolWindow
|
||||
|
||||
# Determine window parameters.
|
||||
content_rect = cocoapy.NSMakeRect(0, 0, self._width, self._height)
|
||||
WindowClass = PygletWindow
|
||||
if self._fullscreen:
|
||||
style_mask = cocoapy.NSBorderlessWindowMask
|
||||
else:
|
||||
if self._style not in self._style_masks:
|
||||
self._style = self.WINDOW_STYLE_DEFAULT
|
||||
style_mask = self._style_masks[self._style]
|
||||
if self._resizable:
|
||||
style_mask |= cocoapy.NSResizableWindowMask
|
||||
if self._style == BaseWindow.WINDOW_STYLE_TOOL:
|
||||
WindowClass = PygletToolWindow
|
||||
# First create an instance of our NSWindow subclass.
|
||||
|
||||
# First create an instance of our NSWindow subclass.
|
||||
# FIX ME:
|
||||
# Need to use this initializer to have any hope of multi-monitor support.
|
||||
# But currently causes problems on Mac OS X Lion. So for now, we initialize the
|
||||
# window without including screen information.
|
||||
#
|
||||
# self._nswindow = WindowClass.alloc().initWithContentRect_styleMask_backing_defer_screen_(
|
||||
# content_rect, # contentRect
|
||||
# style_mask, # styleMask
|
||||
# NSBackingStoreBuffered, # backing
|
||||
# False, # defer
|
||||
# self.screen.get_nsscreen()) # screen
|
||||
|
||||
# FIX ME:
|
||||
# Need to use this initializer to have any hope of multi-monitor support.
|
||||
# But currently causes problems on Mac OS X Lion. So for now, we initialize the
|
||||
# window without including screen information.
|
||||
#
|
||||
# self._nswindow = WindowClass.alloc().initWithContentRect_styleMask_backing_defer_screen_(
|
||||
# content_rect, # contentRect
|
||||
# style_mask, # styleMask
|
||||
# NSBackingStoreBuffered, # backing
|
||||
# False, # defer
|
||||
# self.screen.get_nsscreen()) # screen
|
||||
self._nswindow = WindowClass.alloc().initWithContentRect_styleMask_backing_defer_(
|
||||
content_rect, # contentRect
|
||||
style_mask, # styleMask
|
||||
cocoapy.NSBackingStoreBuffered, # backing
|
||||
False) # defer
|
||||
|
||||
self._nswindow = WindowClass.alloc().initWithContentRect_styleMask_backing_defer_(
|
||||
content_rect, # contentRect
|
||||
style_mask, # styleMask
|
||||
cocoapy.NSBackingStoreBuffered, # backing
|
||||
False) # defer
|
||||
if self._fullscreen:
|
||||
# BUG: I suspect that this doesn't do the right thing when using
|
||||
# multiple monitors (which would be to go fullscreen on the monitor
|
||||
# where the window is located). However I've no way to test.
|
||||
blackColor = NSColor.blackColor()
|
||||
self._nswindow.setBackgroundColor_(blackColor)
|
||||
self._nswindow.setOpaque_(True)
|
||||
self.screen.capture_display()
|
||||
self._nswindow.setLevel_(quartz.CGShieldingWindowLevel())
|
||||
self.context.set_full_screen()
|
||||
self._center_window()
|
||||
self._mouse_in_window = True
|
||||
else:
|
||||
self._set_nice_window_location()
|
||||
self._mouse_in_window = self._mouse_in_content_rect()
|
||||
|
||||
if self._fullscreen:
|
||||
# BUG: I suspect that this doesn't do the right thing when using
|
||||
# multiple monitors (which would be to go fullscreen on the monitor
|
||||
# where the window is located). However I've no way to test.
|
||||
blackColor = NSColor.blackColor()
|
||||
self._nswindow.setBackgroundColor_(blackColor)
|
||||
self._nswindow.setOpaque_(True)
|
||||
self.screen.capture_display()
|
||||
self._nswindow.setLevel_(quartz.CGShieldingWindowLevel())
|
||||
self.context.set_full_screen()
|
||||
self._center_window()
|
||||
self._mouse_in_window = True
|
||||
else:
|
||||
self._set_nice_window_location()
|
||||
self._mouse_in_window = self._mouse_in_content_rect()
|
||||
# Then create a view and set it as our NSWindow's content view.
|
||||
self._nsview = PygletView.alloc().initWithFrame_cocoaWindow_(content_rect, self)
|
||||
self._nswindow.setContentView_(self._nsview)
|
||||
self._nswindow.makeFirstResponder_(self._nsview)
|
||||
|
||||
# Then create a view and set it as our NSWindow's content view.
|
||||
self._nsview = PygletView.alloc().initWithFrame_cocoaWindow_(content_rect, self)
|
||||
self._nswindow.setContentView_(self._nsview)
|
||||
self._nswindow.makeFirstResponder_(self._nsview)
|
||||
# Create a canvas with the view as its drawable and attach context to it.
|
||||
self.canvas = CocoaCanvas(self.display, self.screen, self._nsview)
|
||||
self.context.attach(self.canvas)
|
||||
|
||||
# Create a canvas with the view as its drawable and attach context to it.
|
||||
self.canvas = CocoaCanvas(self.display, self.screen, self._nsview)
|
||||
self.context.attach(self.canvas)
|
||||
# Configure the window.
|
||||
self._nswindow.setAcceptsMouseMovedEvents_(True)
|
||||
self._nswindow.setReleasedWhenClosed_(False)
|
||||
self._nswindow.useOptimizedDrawing_(True)
|
||||
self._nswindow.setPreservesContentDuringLiveResize_(False)
|
||||
|
||||
# Configure the window.
|
||||
self._nswindow.setAcceptsMouseMovedEvents_(True)
|
||||
self._nswindow.setReleasedWhenClosed_(False)
|
||||
self._nswindow.useOptimizedDrawing_(True)
|
||||
self._nswindow.setPreservesContentDuringLiveResize_(False)
|
||||
# Set the delegate.
|
||||
self._delegate = PygletDelegate.alloc().initWithWindow_(self)
|
||||
|
||||
# Set the delegate.
|
||||
self._delegate = PygletDelegate.alloc().initWithWindow_(self)
|
||||
# Configure CocoaWindow.
|
||||
self.set_caption(self._caption)
|
||||
if self._minimum_size is not None:
|
||||
self.set_minimum_size(*self._minimum_size)
|
||||
if self._maximum_size is not None:
|
||||
self.set_maximum_size(*self._maximum_size)
|
||||
|
||||
# Configure CocoaWindow.
|
||||
self.set_caption(self._caption)
|
||||
if self._minimum_size is not None:
|
||||
self.set_minimum_size(*self._minimum_size)
|
||||
if self._maximum_size is not None:
|
||||
self.set_maximum_size(*self._maximum_size)
|
||||
if self._file_drops:
|
||||
array = NSArray.arrayWithObject_(cocoapy.NSPasteboardTypeURL)
|
||||
self._nsview.registerForDraggedTypes_(array)
|
||||
|
||||
if self._file_drops:
|
||||
array = NSArray.arrayWithObject_(cocoapy.NSPasteboardTypeURL)
|
||||
self._nsview.registerForDraggedTypes_(array)
|
||||
|
||||
self.context.update_geometry()
|
||||
self.switch_to()
|
||||
self.set_vsync(self._vsync)
|
||||
self.set_visible(self._visible)
|
||||
|
||||
pool.drain()
|
||||
self.context.update_geometry()
|
||||
self.switch_to()
|
||||
self.set_vsync(self._vsync)
|
||||
self.set_visible(self._visible)
|
||||
|
||||
def _set_nice_window_location(self):
|
||||
# Construct a list of all visible windows that aren't us.
|
||||
@ -208,42 +210,39 @@ class CocoaWindow(BaseWindow):
|
||||
if self._was_closed:
|
||||
return
|
||||
|
||||
# Create a temporary autorelease pool for this method.
|
||||
pool = NSAutoreleasePool.new()
|
||||
with AutoReleasePool():
|
||||
# Restore cursor visibility
|
||||
self.set_mouse_platform_visible(True)
|
||||
self.set_exclusive_mouse(False)
|
||||
self.set_exclusive_keyboard(False)
|
||||
|
||||
# Restore cursor visibility
|
||||
self.set_mouse_platform_visible(True)
|
||||
self.set_exclusive_mouse(False)
|
||||
self.set_exclusive_keyboard(False)
|
||||
# Remove the delegate object
|
||||
if self._delegate:
|
||||
self._nswindow.setDelegate_(None)
|
||||
self._delegate.release()
|
||||
self._delegate = None
|
||||
|
||||
# Remove the delegate object
|
||||
if self._delegate:
|
||||
self._nswindow.setDelegate_(None)
|
||||
self._delegate.release()
|
||||
self._delegate = None
|
||||
# Remove window from display and remove its view.
|
||||
if self._nswindow:
|
||||
self._nswindow.orderOut_(None)
|
||||
self._nswindow.setContentView_(None)
|
||||
self._nswindow.close()
|
||||
|
||||
# Remove window from display and remove its view.
|
||||
if self._nswindow:
|
||||
self._nswindow.orderOut_(None)
|
||||
self._nswindow.setContentView_(None)
|
||||
self._nswindow.close()
|
||||
# Restore screen mode. This also releases the display
|
||||
# if it was captured for fullscreen mode.
|
||||
self.screen.restore_mode()
|
||||
|
||||
# Restore screen mode. This also releases the display
|
||||
# if it was captured for fullscreen mode.
|
||||
self.screen.restore_mode()
|
||||
# Remove view from canvas and then remove canvas.
|
||||
if self.canvas:
|
||||
self.canvas.nsview.release()
|
||||
self.canvas.nsview = None
|
||||
self.canvas = None
|
||||
|
||||
# Remove view from canvas and then remove canvas.
|
||||
if self.canvas:
|
||||
self.canvas.nsview.release()
|
||||
self.canvas.nsview = None
|
||||
self.canvas = None
|
||||
# Do this last, so that we don't see white flash
|
||||
# when exiting application from fullscreen mode.
|
||||
super(CocoaWindow, self).close()
|
||||
|
||||
# Do this last, so that we don't see white flash
|
||||
# when exiting application from fullscreen mode.
|
||||
super(CocoaWindow, self).close()
|
||||
|
||||
self._was_closed = True
|
||||
pool.drain()
|
||||
self._was_closed = True
|
||||
|
||||
def switch_to(self):
|
||||
if self.context:
|
||||
@ -261,26 +260,24 @@ class CocoaWindow(BaseWindow):
|
||||
event = True
|
||||
|
||||
# Dequeue and process all of the pending Cocoa events.
|
||||
pool = NSAutoreleasePool.new()
|
||||
NSApp = NSApplication.sharedApplication()
|
||||
while event and self._nswindow and self._context:
|
||||
event = NSApp.nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
cocoapy.NSAnyEventMask, None, cocoapy.NSEventTrackingRunLoopMode, True)
|
||||
with AutoReleasePool():
|
||||
NSApp = NSApplication.sharedApplication()
|
||||
while event and self._nswindow and self._context:
|
||||
event = NSApp.nextEventMatchingMask_untilDate_inMode_dequeue_(
|
||||
cocoapy.NSAnyEventMask, None, cocoapy.NSEventTrackingRunLoopMode, True)
|
||||
|
||||
if event:
|
||||
event_type = event.type()
|
||||
# Pass on all events.
|
||||
NSApp.sendEvent_(event)
|
||||
# And resend key events to special handlers.
|
||||
if event_type == cocoapy.NSKeyDown and not event.isARepeat():
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletKeyDown:'), None, event)
|
||||
elif event_type == cocoapy.NSKeyUp:
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletKeyUp:'), None, event)
|
||||
elif event_type == cocoapy.NSFlagsChanged:
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletFlagsChanged:'), None, event)
|
||||
NSApp.updateWindows()
|
||||
|
||||
pool.drain()
|
||||
if event:
|
||||
event_type = event.type()
|
||||
# Pass on all events.
|
||||
NSApp.sendEvent_(event)
|
||||
# And resend key events to special handlers.
|
||||
if event_type == cocoapy.NSKeyDown and not event.isARepeat():
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletKeyDown:'), None, event)
|
||||
elif event_type == cocoapy.NSKeyUp:
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletKeyUp:'), None, event)
|
||||
elif event_type == cocoapy.NSFlagsChanged:
|
||||
NSApp.sendAction_to_from_(cocoapy.get_selector('pygletFlagsChanged:'), None, event)
|
||||
NSApp.updateWindows()
|
||||
|
||||
self._allow_dispatch_event = False
|
||||
|
||||
@ -486,25 +483,25 @@ class CocoaWindow(BaseWindow):
|
||||
if name == self.CURSOR_DEFAULT:
|
||||
return DefaultMouseCursor()
|
||||
cursors = {
|
||||
self.CURSOR_CROSSHAIR: 'crosshairCursor',
|
||||
self.CURSOR_HAND: 'pointingHandCursor',
|
||||
self.CURSOR_HELP: 'arrowCursor',
|
||||
self.CURSOR_NO: 'operationNotAllowedCursor', # Mac OS 10.6
|
||||
self.CURSOR_SIZE: 'arrowCursor',
|
||||
self.CURSOR_SIZE_UP: 'resizeUpCursor',
|
||||
self.CURSOR_SIZE_UP_RIGHT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_RIGHT: 'resizeRightCursor',
|
||||
self.CURSOR_CROSSHAIR: 'crosshairCursor',
|
||||
self.CURSOR_HAND: 'pointingHandCursor',
|
||||
self.CURSOR_HELP: 'arrowCursor',
|
||||
self.CURSOR_NO: 'operationNotAllowedCursor', # Mac OS 10.6
|
||||
self.CURSOR_SIZE: 'arrowCursor',
|
||||
self.CURSOR_SIZE_UP: 'resizeUpCursor',
|
||||
self.CURSOR_SIZE_UP_RIGHT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_RIGHT: 'resizeRightCursor',
|
||||
self.CURSOR_SIZE_DOWN_RIGHT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_DOWN: 'resizeDownCursor',
|
||||
self.CURSOR_SIZE_DOWN_LEFT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_LEFT: 'resizeLeftCursor',
|
||||
self.CURSOR_SIZE_UP_LEFT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_UP_DOWN: 'resizeUpDownCursor',
|
||||
self.CURSOR_SIZE_DOWN: 'resizeDownCursor',
|
||||
self.CURSOR_SIZE_DOWN_LEFT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_LEFT: 'resizeLeftCursor',
|
||||
self.CURSOR_SIZE_UP_LEFT: 'arrowCursor',
|
||||
self.CURSOR_SIZE_UP_DOWN: 'resizeUpDownCursor',
|
||||
self.CURSOR_SIZE_LEFT_RIGHT: 'resizeLeftRightCursor',
|
||||
self.CURSOR_TEXT: 'IBeamCursor',
|
||||
self.CURSOR_WAIT: 'arrowCursor', # No wristwatch cursor in Cocoa
|
||||
self.CURSOR_WAIT_ARROW: 'arrowCursor', # No wristwatch cursor in Cocoa
|
||||
}
|
||||
self.CURSOR_TEXT: 'IBeamCursor',
|
||||
self.CURSOR_WAIT: 'arrowCursor', # No wristwatch cursor in Cocoa
|
||||
self.CURSOR_WAIT_ARROW: 'arrowCursor', # No wristwatch cursor in Cocoa
|
||||
}
|
||||
if name not in cursors:
|
||||
raise RuntimeError('Unknown cursor name "%s"' % name)
|
||||
return CocoaMouseCursor(cursors[name])
|
||||
@ -514,7 +511,7 @@ class CocoaWindow(BaseWindow):
|
||||
# If absolute, then x, y is given in global display coordinates
|
||||
# which sets (0,0) at top left corner of main display. It is possible
|
||||
# to warp the mouse position to a point inside of another display.
|
||||
quartz.CGWarpMouseCursorPosition(CGPoint(x,y))
|
||||
quartz.CGWarpMouseCursorPosition(CGPoint(x, y))
|
||||
else:
|
||||
# Window-relative coordinates: (x, y) are given in window coords
|
||||
# with (0,0) at bottom-left corner of window and y up. We find
|
||||
@ -538,7 +535,7 @@ class CocoaWindow(BaseWindow):
|
||||
# Move mouse to center of window.
|
||||
frame = self._nswindow.frame()
|
||||
width, height = frame.size.width, frame.size.height
|
||||
self.set_mouse_position(width/2, height/2)
|
||||
self.set_mouse_position(width / 2, height / 2)
|
||||
quartz.CGAssociateMouseAndMouseCursorPosition(False)
|
||||
else:
|
||||
quartz.CGAssociateMouseAndMouseCursorPosition(True)
|
||||
|
@ -293,7 +293,10 @@ F17 = 0xffce
|
||||
F18 = 0xffcf
|
||||
F19 = 0xffd0
|
||||
F20 = 0xffd1
|
||||
|
||||
F21 = 0xffd2
|
||||
F22 = 0xffd3
|
||||
F23 = 0xffd4
|
||||
F24 = 0xffd5
|
||||
# Modifiers
|
||||
LSHIFT = 0xffe1
|
||||
RSHIFT = 0xffe2
|
||||
|
Loading…
Reference in New Issue
Block a user