pyglet fetch up

This commit is contained in:
shenjack 2023-03-22 00:08:03 +08:00
parent 43532f8bec
commit 96b270b096
41 changed files with 1307 additions and 1307 deletions

View File

@ -125,13 +125,13 @@ options = {
'xlib_fullscreen_override_redirect': False,
'search_local_libs': True,
'win32_gdi_font': False,
'scale_with_dpi': False,
'headless': False,
'headless_device': 0,
'win32_disable_shaping': False,
'dw_legacy_naming': False,
'win32_disable_xinput': False,
'com_mta': False,
'osx_alt_loop': False
}
_option_types = {
@ -156,7 +156,6 @@ _option_types = {
'vsync': bool,
'xsync': bool,
'xlib_fullscreen_override_redirect': bool,
'scale_with_dpi': bool,
'search_local_libs': bool,
'win32_gdi_font': bool,
'headless': bool,
@ -164,7 +163,8 @@ _option_types = {
'win32_disable_shaping': bool,
'dw_legacy_naming': bool,
'win32_disable_xinput': bool,
'com_mta': bool
'com_mta': bool,
'osx_alt_loop': bool,
}
@ -333,7 +333,6 @@ 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
@ -354,7 +353,6 @@ if TYPE_CHECKING:
from . import text
from . import window
else:
animation = _ModuleProxy('animation')
app = _ModuleProxy('app')
canvas = _ModuleProxy('canvas')
clock = _ModuleProxy('clock')

View File

@ -1,142 +0,0 @@
"""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})"

View File

@ -29,20 +29,24 @@ default policy is to wait until all windows are closed)::
.. versionadded:: 1.1
"""
import platform
import sys
import weakref
from pyglet.app.base import EventLoop
import pyglet
from pyglet import compat_platform
from pyglet.app.base import EventLoop
_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
if _is_pyglet_doc_run:
from pyglet.app.base import PlatformEventLoop
else:
if compat_platform == 'darwin':
from pyglet.app.cocoa import CocoaEventLoop as PlatformEventLoop
from pyglet.app.cocoa import CocoaPlatformEventLoop as PlatformEventLoop
if platform.machine() == 'arm64' or pyglet.options["osx_alt_loop"]:
from pyglet.app.cocoa import CocoaAlternateEventLoop as EventLoop
elif compat_platform in ('win32', 'cygwin'):
from pyglet.app.win32 import Win32EventLoop as PlatformEventLoop
else:
@ -61,7 +65,7 @@ the set when they are no longer referenced or are closed explicitly.
"""
def run(interval=1/60):
def run(interval=1 / 60):
"""Begin processing events, scheduled functions and window updates.
This is a convenience function, equivalent to::

View File

@ -1,5 +1,8 @@
from pyglet.app.base import PlatformEventLoop
from pyglet.libs.darwin import cocoapy, AutoReleasePool
import signal
from pyglet import app
from pyglet.app.base import PlatformEventLoop, EventLoop
from pyglet.libs.darwin import cocoapy, AutoReleasePool, ObjCSubclass, PyObjectEncoding, ObjCInstance, send_super, \
ObjCClass, get_selector
NSApplication = cocoapy.ObjCClass('NSApplication')
NSMenu = cocoapy.ObjCClass('NSMenu')
@ -7,7 +10,7 @@ NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
NSDate = cocoapy.ObjCClass('NSDate')
NSEvent = cocoapy.ObjCClass('NSEvent')
NSUserDefaults = cocoapy.ObjCClass('NSUserDefaults')
NSTimer = cocoapy.ObjCClass('NSTimer')
@ -46,10 +49,77 @@ def create_menu():
appMenuItem.release()
class CocoaEventLoop(PlatformEventLoop):
class _AppDelegate_Implementation:
_AppDelegate = ObjCSubclass('NSObject', '_AppDelegate')
@_AppDelegate.method(b'@' + PyObjectEncoding)
def init(self, pyglet_loop):
objc = ObjCInstance(send_super(self, 'init'))
self._pyglet_loop = pyglet_loop
return objc # objc is self
@_AppDelegate.method('v')
def updatePyglet_(self):
self._pyglet_loop.nsapp_step()
@_AppDelegate.method('v@')
def applicationWillTerminate_(self, notification):
self._pyglet_loop.is_running = False
self._pyglet_loop.has_exit = True
@_AppDelegate.method('v@')
def applicationDidFinishLaunching_(self, notification):
self._pyglet_loop._finished_launching = True
_AppDelegate = ObjCClass('_AppDelegate') # the actual class
class CocoaAlternateEventLoop(EventLoop):
"""This is an alternate loop developed mainly for ARM64 variants of macOS.
nextEventMatchingMask_untilDate_inMode_dequeue_ is very broken with ctypes calls. Events eventually stop
working properly after X returns. This event loop differs in that it uses the built-in NSApplication event
loop. We tie our schedule into it via timer.
"""
def run(self, interval=1/60):
if not interval:
self.clock.schedule(self._redraw_windows)
else:
self.clock.schedule_interval(self._redraw_windows, interval)
self.has_exit = False
from pyglet.window import Window
Window._enable_event_queue = False
# Dispatch pending events
for window in app.windows:
window.switch_to()
window.dispatch_pending_events()
self.platform_event_loop = app.platform_event_loop
self.dispatch_event('on_enter')
self.is_running = True
self.platform_event_loop.nsapp_start(interval)
def exit(self):
"""Safely exit the event loop at the end of the current iteration.
This method is a thread-safe equivalent for setting
:py:attr:`has_exit` to ``True``. All waiting threads will be
interrupted (see :py:meth:`sleep`).
"""
self.has_exit = True
self.platform_event_loop.notify()
self.is_running = False
self.dispatch_event('on_exit')
self.platform_event_loop.nsapp_stop()
class CocoaPlatformEventLoop(PlatformEventLoop):
def __init__(self):
super(CocoaEventLoop, self).__init__()
super(CocoaPlatformEventLoop, self).__init__()
with AutoReleasePool():
# Prepare the default application.
self.NSApp = NSApplication.sharedApplication()
@ -82,6 +152,44 @@ class CocoaEventLoop(PlatformEventLoop):
self.NSApp.activateIgnoringOtherApps_(True)
self._finished_launching = True
def nsapp_start(self, interval):
"""Used only for CocoaAlternateEventLoop"""
from pyglet.app import event_loop
self._event_loop = event_loop
def term_received(*args):
if self.timer:
self.timer.invalidate()
self.timer = None
self.nsapp_stop()
# Force NSApp to close if Python receives sig events.
signal.signal(signal.SIGINT, term_received)
signal.signal(signal.SIGTERM, term_received)
self.appdelegate = _AppDelegate.alloc().init(self)
self.NSApp.setDelegate_(self.appdelegate)
self.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
interval, # Clamped internally to 0.0001 (including 0)
self.appdelegate,
get_selector('updatePyglet:'),
False,
True
)
self.NSApp.run()
def nsapp_step(self):
"""Used only for CocoaAlternateEventLoop"""
self._event_loop.idle()
self.dispatch_posted_events()
def nsapp_stop(self):
"""Used only for CocoaAlternateEventLoop"""
self.NSApp.terminate_(None)
def step(self, timeout=None):
with AutoReleasePool():
self.dispatch_posted_events()
@ -107,25 +215,8 @@ class CocoaEventLoop(PlatformEventLoop):
if event is not None:
event_type = event.type()
if event_type != cocoapy.NSApplicationDefined:
# Send out event as normal. Responders will still receive
# keyUp:, keyDown:, and flagsChanged: events.
self.NSApp.sendEvent_(event)
# Resend key events as special pyglet-specific messages
# which supplant the keyDown:, keyUp:, and flagsChanged: messages
# because NSApplication translates multiple key presses into key
# equivalents before sending them on, which means that some keyUp:
# messages are never sent for individual keys. Our pyglet-specific
# replacements ensure that we see all the raw key presses & releases.
# We also filter out key-down repeats since pyglet only sends one
# on_key_press event per key press.
if event_type == cocoapy.NSKeyDown and not event.isARepeat():
self.NSApp.sendAction_to_from_(cocoapy.get_selector("pygletKeyDown:"), None, event)
elif event_type == cocoapy.NSKeyUp:
self.NSApp.sendAction_to_from_(cocoapy.get_selector("pygletKeyUp:"), None, event)
elif event_type == cocoapy.NSFlagsChanged:
self.NSApp.sendAction_to_from_(cocoapy.get_selector("pygletFlagsChanged:"), None, event)
self.NSApp.updateWindows()
did_time_out = False
else:

View File

@ -267,20 +267,6 @@ class Screen:
"""
raise NotImplementedError('abstract')
def get_dpi(self):
"""Get the DPI of the screen.
:rtype: int
"""
raise NotImplementedError('abstract')
def get_scale(self):
"""Get the pixel scale ratio of the screen.
:rtype: float
"""
raise NotImplementedError('abstract')
class ScreenMode:
"""Screen resolution and display settings.

View File

@ -4,11 +4,9 @@ from ctypes import *
from .base import Display, Screen, ScreenMode, Canvas
from pyglet.libs.darwin.cocoapy import CGDirectDisplayID, quartz, cf, ObjCClass, get_NSString
from pyglet.libs.darwin.cocoapy import CGDirectDisplayID, quartz, cf
from pyglet.libs.darwin.cocoapy import cfstring_to_string, cfarray_to_list
from ..libs.darwin import NSDeviceResolution
NSScreen = ObjCClass('NSScreen')
class CocoaDisplay(Display):
@ -33,40 +31,28 @@ class CocoaScreen(Screen):
self._cg_display_id = displayID
# Save the default mode so we can restore to it.
self._default_mode = self.get_mode()
self._ns_screen = self.get_nsscreen()
def get_nsscreen(self):
"""Returns the NSScreen instance that matches our CGDirectDisplayID."""
# Get a list of all currently active NSScreens and then search through
# them until we find one that matches our CGDisplayID.
screen_array = NSScreen.screens()
count = screen_array.count()
for i in range(count):
nsscreen = screen_array.objectAtIndex_(i)
screenInfo = nsscreen.deviceDescription()
displayID = screenInfo.objectForKey_(get_NSString('NSScreenNumber'))
displayID = displayID.intValue()
if displayID == self._cg_display_id:
return nsscreen
return None
def get_dpi(self):
desc = self._ns_screen.deviceDescription()
#psize = desc.objectForKey_(NSDeviceSize).sizeValue()
rsize = desc.objectForKey_(NSDeviceResolution).sizeValue()
return int(rsize.width)
def get_scale(self):
ratio = 1.0
if self._ns_screen:
pts = self._ns_screen.frame()
pixels = self._ns_screen.convertRectToBacking_(pts)
ratio = pixels.size.width / pts.size.width
else:
print("Could not initialize NSScreen to retrieve DPI. Using default.")
print("BSF", self._ns_screen.backingScaleFactor(), "ratio:", ratio)
return ratio
# FIX ME:
# This method is needed to get multi-monitor support working properly.
# However the NSScreens.screens() message currently sends out a warning:
# "*** -[NSLock unlock]: lock (<NSLock: 0x...> '(null)') unlocked when not locked"
# on Snow Leopard and apparently causes python to crash on Lion.
#
# def get_nsscreen(self):
# """Returns the NSScreen instance that matches our CGDirectDisplayID."""
# NSScreen = ObjCClass('NSScreen')
# # Get a list of all currently active NSScreens and then search through
# # them until we find one that matches our CGDisplayID.
# screen_array = NSScreen.screens()
# count = screen_array.count()
# for i in range(count):
# nsscreen = screen_array.objectAtIndex_(i)
# screenInfo = nsscreen.deviceDescription()
# displayID = screenInfo.objectForKey_(get_NSString('NSScreenNumber'))
# displayID = displayID.intValue()
# if displayID == self._cg_display_id:
# return nsscreen
# return None
def get_matching_configs(self, template):
canvas = CocoaCanvas(self.display, self, None)

View File

@ -1,22 +1,9 @@
from .base import Display, Screen, ScreenMode, Canvas
from pyglet.libs.win32 import _kernel32, _user32, _shcore, _gdi32, types, constants
from pyglet.libs.win32 import _user32
from pyglet.libs.win32.constants import *
from pyglet.libs.win32.types import *
def set_dpi_awareness():
"""
Setting DPI varies per Windows version.
Note: DPI awareness needs to be set before Window, Display, or Screens are initialized.
"""
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
_user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
elif WINDOWS_8_1_OR_GREATER: # 8.1 and above allows per monitor DPI.
_shcore.SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)
elif WINDOWS_VISTA_OR_GREATER: # Only System wide DPI
_user32.SetProcessDPIAware()
set_dpi_awareness()
class Win32Display(Display):
def get_screens(self):
@ -57,24 +44,6 @@ class Win32Screen(Screen):
info.cbSize = sizeof(MONITORINFOEX)
_user32.GetMonitorInfoW(self._handle, byref(info))
return info.szDevice
def get_dpi(self):
if WINDOWS_8_1_OR_GREATER:
xdpi = UINT()
ydpi = UINT()
_shcore.GetDpiForMonitor(self._handle, 0, byref(xdpi), byref(ydpi))
xdpi, ydpi = xdpi.value, ydpi.value
else:
dc = _user32.GetDC(None)
xdpi = _gdi32.GetDeviceCaps(dc, LOGPIXELSX)
ydpi = _gdi32.GetDeviceCaps(dc, LOGPIXELSY)
_user32.ReleaseDC(0, dc)
return xdpi
def get_scale(self):
xdpi = self.get_dpi()
return xdpi / USER_DEFAULT_SCREEN_DPI
def get_modes(self):
device_name = self.get_device_name()

View File

@ -6,7 +6,6 @@ from pyglet.app.xlib import XlibSelectDevice
from .base import Display, Screen, ScreenMode, Canvas
from . import xlib_vidmoderestore
from ..util import asbytes
# XXX
@ -178,28 +177,6 @@ class XlibScreen(Screen):
super(XlibScreen, self).__init__(display, x, y, width, height)
self._xinerama = xinerama
def get_dpi(self):
resource = xlib.XResourceManagerString(self.display._display)
dpi = 96
if resource:
xlib.XrmInitialize()
db = xlib.XrmGetStringDatabase(resource)
if db:
rs_type = c_char_p()
value = xlib.XrmValue()
if xlib.XrmGetResource(db, asbytes("Xft.dpi"), asbytes("Xft.dpi"),
byref(rs_type), byref(value)):
if value.addr and rs_type.value == b'String':
dpi = int(value.addr)
xlib.XrmDestroyDatabase(db)
return dpi
def get_scale(self):
return self.get_dpi() / 96
def get_matching_configs(self, template):
canvas = XlibCanvas(self.display, None)
configs = template.match(canvas)

View File

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

View File

@ -2,13 +2,12 @@
import math
import warnings
from ctypes import c_void_p, c_int32, byref, c_byte, c_buffer
from ctypes import c_void_p, c_int32, byref, c_byte
from pyglet.font import base
import pyglet.image
from pyglet.libs.darwin import cocoapy, kCTFontURLAttribute, kCFStringEncodingUTF8
from pyglet.window.cocoa.pyglet_view import NSURL
from pyglet.libs.darwin import cocoapy, kCTFontURLAttribute
cf = cocoapy.cf
ct = cocoapy.ct

View File

@ -1,8 +1,6 @@
import platform
from ctypes import c_uint32, c_int, byref
import pyglet
from pyglet.gl.base import Config, CanvasConfig, Context
from pyglet.gl import ContextException
@ -262,7 +260,7 @@ class CocoaContext(Context):
# The NSView instance should be attached to a nondeferred window before calling
# setView, otherwise you get an "invalid drawable" message.
self._nscontext.setView_(canvas.nsview)
self._nscontext.view().setWantsBestResolutionOpenGLSurface_(1)
self.set_current()
def detach(self):

View File

@ -57,11 +57,38 @@ _uniform_setters = {
GL_FLOAT_VEC3: (GLfloat, glUniform3fv, glProgramUniform3fv, 3, 1),
GL_FLOAT_VEC4: (GLfloat, glUniform4fv, glProgramUniform4fv, 4, 1),
GL_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
# 1D Samplers
GL_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_SAMPLER_1D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_1D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
# 2D Samplers
GL_SAMPLER_2D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_2D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_2D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
# Multisample
GL_SAMPLER_2D_MULTISAMPLE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_2D_MULTISAMPLE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
# Cube Samplers
GL_SAMPLER_CUBE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_CUBE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_CUBE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_SAMPLER_CUBE_MAP_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_CUBE_MAP_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
# 3D Samplers
GL_SAMPLER_3D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_SAMPLER_3D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_UNSIGNED_INT_SAMPLER_3D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_FLOAT_MAT2: (GLfloat, glUniformMatrix2fv, glProgramUniformMatrix2fv, 4, 1),
GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, glProgramUniformMatrix3fv, 6, 1),

View File

@ -97,17 +97,19 @@ import re
import weakref
from ctypes import *
from io import open
from io import open, BytesIO
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
@ -457,7 +459,7 @@ class AbstractImageSequence:
.. versionadded:: 1.1
"""
return Animation.from_sequence(self, period, loop)
return Animation.from_image_sequence(self, period, loop)
def __getitem__(self, slice):
"""Retrieve a (list of) image.
@ -1424,6 +1426,12 @@ class Texture(AbstractImage):
order = self.tex_coords_order
self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
@property
def uv(self):
"""Tuple containing the left, bottom, right, top 2D texture coordinates."""
tex_coords = self.tex_coords
return tex_coords[0], tex_coords[1], tex_coords[3], tex_coords[7]
def __repr__(self):
return "{}(id={}, size={}x{})".format(self.__class__.__name__, self.id, self.width, self.height)

View File

@ -0,0 +1,179 @@
"""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)

View File

@ -4,7 +4,6 @@ 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

View File

@ -107,20 +107,20 @@ class XInputDevice(DeviceResponder, Device):
super(XInputDevice, self).open(window, exclusive)
if window is None:
self.is_open = False
self._is_open = False
raise DeviceOpenException('XInput devices require a window')
if window.display._display != self.display._display:
self.is_open = False
self._is_open = False
raise DeviceOpenException('Window and device displays differ')
if exclusive:
self.is_open = False
self._is_open = False
raise DeviceOpenException('Cannot open XInput device exclusive')
self._device = xi.XOpenDevice(self.display._display, self._device_id)
if not self._device:
self.is_open = False
self._is_open = False
raise DeviceOpenException('Cannot open device')
self._install_events(window)

View File

@ -215,6 +215,7 @@ NSDefaultRunLoopMode = c_void_p.in_dll(appkit, 'NSDefaultRunLoopMode')
NSEventTrackingRunLoopMode = c_void_p.in_dll(appkit, 'NSEventTrackingRunLoopMode')
NSApplicationDidHideNotification = c_void_p.in_dll(appkit, 'NSApplicationDidHideNotification')
NSApplicationDidUnhideNotification = c_void_p.in_dll(appkit, 'NSApplicationDidUnhideNotification')
NSApplicationDidUpdateNotification = c_void_p.in_dll(appkit, 'NSApplicationDidUpdateNotification')
NSPasteboardURLReadingFileURLsOnlyKey = c_void_p.in_dll(appkit, 'NSPasteboardURLReadingFileURLsOnlyKey')
NSPasteboardTypeURL = c_void_p.in_dll(appkit, 'NSPasteboardTypeURL')
NSDragOperationGeneric = 4
@ -227,6 +228,17 @@ NSKeyUp = 11
NSFlagsChanged = 12
NSApplicationDefined = 15
# Undocumented left/right modifier masks found by experimentation:
NSLeftShiftKeyMask = 1 << 1
NSRightShiftKeyMask = 1 << 2
NSLeftControlKeyMask = 1 << 0
NSRightControlKeyMask = 1 << 13
NSLeftAlternateKeyMask = 1 << 5
NSRightAlternateKeyMask = 1 << 6
NSLeftCommandKeyMask = 1 << 3
NSRightCommandKeyMask = 1 << 4
NSAlphaShiftKeyMask = 1 << 16
NSShiftKeyMask = 1 << 17
NSControlKeyMask = 1 << 18
@ -400,9 +412,6 @@ quartz.CGDisplayCopyDisplayMode.argtypes = [CGDirectDisplayID]
quartz.CGDisplayModeGetRefreshRate.restype = c_double
quartz.CGDisplayModeGetRefreshRate.argtypes = [c_void_p]
quartz.CGDisplayScreenSize.restype = CGSize
quartz.CGDisplayScreenSize.argtypes = [CGDirectDisplayID]
quartz.CGDisplayModeRetain.restype = c_void_p
quartz.CGDisplayModeRetain.argtypes = [c_void_p]

View File

@ -28,7 +28,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import ctypes
import sys
import platform
import struct
@ -415,15 +415,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]
######################################################################
# void *objc_autoreleasePoolPush(void)
objc.objc_autoreleasePoolPush.restype = c_void_p
objc.objc_autoreleasePoolPush.argtypes = []
# void objc_autoreleasePoolPop(void *pool)
objc.objc_autoreleasePoolPop.restype = None
objc.objc_autoreleasePoolPop.argtypes = [c_void_p]
######################################################################
# void *objc_autoreleasePoolPush(void)
@ -434,14 +425,6 @@ objc.objc_autoreleasePoolPush.argtypes = []
objc.objc_autoreleasePoolPop.restype = None
objc.objc_autoreleasePoolPop.argtypes = [c_void_p]
# id objc_autoreleaseReturnValue(id value)
objc.objc_autoreleaseReturnValue.restype = c_void_p
objc.objc_autoreleaseReturnValue.argtypes = [c_void_p]
# id objc_autoreleaseReturnValue(id value)
objc.objc_autorelease.restype = c_void_p
objc.objc_autorelease.argtypes = [c_void_p]
######################################################################
# Constants
OBJC_ASSOCIATION_ASSIGN = 0 # Weak reference to the associated object.
@ -567,7 +550,7 @@ def send_super(receiver, selName, *args, superclass_name=None, **kwargs):
if argtypes:
objc.objc_msgSendSuper.argtypes = [OBJC_SUPER_PTR, c_void_p] + argtypes
else:
objc.objc_msgSendSuper.argtypes = [OBJC_SUPER_PTR, c_void_p]
objc.objc_msgSendSuper.argtypes = None
result = objc.objc_msgSendSuper(byref(super_struct), selector, *args)
if restype == c_void_p:
result = c_void_p(result)
@ -738,12 +721,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 = {}
@ -761,7 +744,6 @@ class ObjCMethod:
self.nargs = objc.method_getNumberOfArguments(method)
self.imp = c_void_p(objc.method_getImplementation(method))
self.argument_types = []
for i in range(self.nargs):
buffer = c_buffer(512)
@ -772,7 +754,7 @@ class ObjCMethod:
try:
self.argtypes = [self.ctype_for_encoding(t) for t in self.argument_types]
except:
# print('no argtypes encoding for %s (%s)' % (self.name, self.argument_types))
# print(f'no argtypes encoding for {self.name} ({self.argument_types})')
self.argtypes = None
# Get types for the return type.
@ -784,7 +766,7 @@ class ObjCMethod:
else:
self.restype = self.ctype_for_encoding(self.return_type)
except:
# print('no restype encoding for %s (%s)' % (self.name, self.return_type))
# print(f'no restype encoding for {self.name} ({self.return_type})')
self.restype = None
self.func = None
@ -820,7 +802,6 @@ class ObjCMethod:
self.prototype = CFUNCTYPE(c_void_p, *self.argtypes)
else:
self.prototype = CFUNCTYPE(self.restype, *self.argtypes)
return self.prototype
def __repr__(self):
@ -845,8 +826,7 @@ class ObjCMethod:
f = self.get_callable()
try:
result = f(objc_id, self.selector, *args)
# if result != None:
# print("result1", self, result, self.restype)
# Convert result to python type if it is a instance or class pointer.
if self.restype == ObjCInstance:
result = ObjCInstance(result)
@ -910,8 +890,8 @@ class ObjCClass:
# Check if we've already created a Python object for this class
# and if so, return it rather than making a new one.
#if name in cls._registered_classes:
# return cls._registered_classes[name]
if name in cls._registered_classes:
return cls._registered_classes[name]
# Otherwise create a new Python object and then initialize it.
objc_class = super(ObjCClass, cls).__new__(cls)
@ -922,11 +902,11 @@ class ObjCClass:
objc_class._as_parameter_ = ptr # for ctypes argument passing
# Store the new class in dictionary of registered classes.
#cls._registered_classes[name] = objc_class
cls._registered_classes[name] = objc_class
# Not sure this is necessary...
#objc_class.cache_instance_methods()
#objc_class.cache_class_methods()
objc_class.cache_instance_methods()
objc_class.cache_class_methods()
return objc_class
@ -971,7 +951,7 @@ class ObjCClass:
method = c_void_p(objc.class_getInstanceMethod(self.ptr, selector))
if method.value:
objc_method = ObjCMethod(method)
#self.instance_methods[name] = objc_method
self.instance_methods[name] = objc_method
return objc_method
return None
@ -988,7 +968,7 @@ class ObjCClass:
method = c_void_p(objc.class_getClassMethod(self.ptr, selector))
if method.value:
objc_method = ObjCMethod(method)
#self.class_methods[name] = objc_method
self.class_methods[name] = objc_method
return objc_method
return None
@ -1135,9 +1115,6 @@ def get_cached_instances():
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."""

View File

@ -1,5 +1,5 @@
import ctypes
from ctypes import c_void_p, c_int, c_bool, Structure, c_uint32, util, cdll, c_uint, c_double, POINTER, c_int64
from ctypes import c_void_p, c_int, c_bool, Structure, c_uint32, util, cdll, c_uint, c_double, POINTER, c_int64, \
CFUNCTYPE
from pyglet.libs.darwin import CFURLRef
@ -74,8 +74,8 @@ ca.ExtAudioFileOpenURL.argtypes = [CFURLRef, ExtAudioFileRef]
AudioFileTypeID = c_uint32
AudioFileID = c_void_p
AudioFile_ReadProc = ctypes.CFUNCTYPE(c_int, c_void_p, ctypes.c_int64, c_uint32, c_void_p, POINTER(c_uint32))
AudioFile_GetSizeProc = ctypes.CFUNCTYPE(ctypes.c_int64, c_void_p)
AudioFile_ReadProc = CFUNCTYPE(c_int, c_void_p, c_int64, c_uint32, c_void_p, POINTER(c_uint32))
AudioFile_GetSizeProc = CFUNCTYPE(c_int64, c_void_p)
ca.AudioFileOpenWithCallbacks.restype = OSStatus
ca.AudioFileOpenWithCallbacks.argtypes = [c_void_p, AudioFile_ReadProc, c_void_p, AudioFile_GetSizeProc, c_void_p,

View File

@ -11,55 +11,15 @@ IS64 = struct.calcsize("P") == 8
_debug_win32 = pyglet.options['debug_win32']
if _debug_win32:
import traceback
_GetLastError = windll.kernel32.GetLastError
_SetLastError = windll.kernel32.SetLastError
_FormatMessageA = windll.kernel32.FormatMessageA
DebugLibrary = lambda lib: ctypes.WinDLL(lib, use_last_error=True if _debug_win32 else False)
_log_win32 = open('debug_win32.log', 'w')
def format_error(err):
msg = create_string_buffer(256)
_FormatMessageA(constants.FORMAT_MESSAGE_FROM_SYSTEM,
c_void_p(),
err,
0,
msg,
len(msg),
c_void_p())
return msg.value
class DebugLibrary:
def __init__(self, lib):
self.lib = lib
def __getattr__(self, name):
fn = getattr(self.lib, name)
def f(*args):
_SetLastError(0)
result = fn(*args)
err = _GetLastError()
if err != 0:
for entry in traceback.format_list(traceback.extract_stack()[:-1]):
_log_win32.write(entry)
print(format_error(err), file=_log_win32)
return result
return f
else:
DebugLibrary = lambda lib: lib
_gdi32 = DebugLibrary(windll.gdi32)
_kernel32 = DebugLibrary(windll.kernel32)
_user32 = DebugLibrary(windll.user32)
_dwmapi = DebugLibrary(windll.dwmapi)
_shell32 = DebugLibrary(windll.shell32)
_ole32 = DebugLibrary(windll.ole32)
_oleaut32 = DebugLibrary(windll.oleaut32)
_shcore = DebugLibrary(windll.shcore)
_gdi32 = DebugLibrary('gdi32')
_kernel32 = DebugLibrary('kernel32')
_user32 = DebugLibrary('user32')
_dwmapi = DebugLibrary('dwmapi')
_shell32 = DebugLibrary('shell32')
_ole32 = DebugLibrary('ole32')
_oleaut32 = DebugLibrary('oleaut32')
# _gdi32
_gdi32.AddFontMemResourceEx.restype = HANDLE
@ -221,9 +181,7 @@ _user32.SetFocus.argtypes = [HWND]
_user32.SetForegroundWindow.restype = BOOL
_user32.SetForegroundWindow.argtypes = [HWND]
_user32.SetTimer.restype = UINT_PTR
_user32.SetTimer.argtypes = [HWND, UINT_PTR, UINT, POINTER(TIMERPROC)]
_user32.KillTimer.restype = UINT_PTR
_user32.KillTimer.argtypes = [HWND, UINT_PTR]
_user32.SetTimer.argtypes = [HWND, UINT_PTR, UINT, TIMERPROC]
_user32.SetWindowLongW.restype = LONG
_user32.SetWindowLongW.argtypes = [HWND, c_int, LONG]
_user32.SetWindowPos.restype = BOOL
@ -249,20 +207,11 @@ _user32.GetRawInputData.restype = UINT
_user32.GetRawInputData.argtypes = [HRAWINPUT, UINT, LPVOID, PUINT, UINT]
_user32.ChangeWindowMessageFilterEx.restype = BOOL
_user32.ChangeWindowMessageFilterEx.argtypes = [HWND, UINT, DWORD, c_void_p]
_user32.SetProcessDPIAware.restype = BOOL
_user32.SetProcessDPIAware.argtypes = []
_user32.MonitorFromWindow.restype = HMONITOR
_user32.MonitorFromWindow.argtypes = [HWND, DWORD]
_user32.RegisterDeviceNotificationW.restype = HANDLE
_user32.RegisterDeviceNotificationW.argtypes = [HANDLE, LPVOID, DWORD]
_user32.UnregisterDeviceNotification.restype = BOOL
_user32.UnregisterDeviceNotification.argtypes = [HANDLE]
if constants.WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
_user32.SetProcessDpiAwarenessContext.restype = BOOL
_user32.SetProcessDpiAwarenessContext.argtypes = [DPI_AWARENESS_CONTEXT]
if constants.WINDOWS_10_ANNIVERSARY_UPDATE_OR_GREATER:
_user32.EnableNonClientDpiScaling.restype = BOOL
_user32.EnableNonClientDpiScaling.argtypes = [HWND]
_user32.GetDpiForWindow.restype = UINT
_user32.GetDpiForWindow.argtypes = [HWND]
# dwmapi
_dwmapi.DwmIsCompositionEnabled.restype = c_int

View File

@ -1492,8 +1492,6 @@ WM_IME_KEYDOWN = 656
WM_IME_KEYUP = 657
WM_MOUSEHOVER = 673
WM_MOUSELEAVE = 675
WM_DPICHANGED = 736
WM_GETDPISCALEDSIZE = 740
WM_CUT = 768
WM_COPY = 769
WM_PASTE = 770
@ -5080,4 +5078,3 @@ DBT_DEVTYP_DEVICEINTERFACE = 5
DEVICE_NOTIFY_WINDOW_HANDLE = 0
DEVICE_NOTIFY_SERVICE_HANDLE = 1
USER_DEFAULT_SCREEN_DPI = 96

View File

@ -71,28 +71,6 @@ TIMERPROC = WINFUNCTYPE(None, HWND, UINT, POINTER(UINT), DWORD)
TIMERAPCPROC = WINFUNCTYPE(None, PVOID, DWORD, DWORD)
MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, LPRECT, LPARAM)
PROCESS_DPI_AWARENESS = UINT
PROCESS_DPI_UNAWARE = 0
PROCESS_SYSTEM_DPI_AWARE = 1
PROCESS_PER_MONITOR_DPI_AWARE = 2
MONITOR_DPI_TYPE = UINT
MDT_EFFECTIVE_DPI = 0
MDT_ANGULAR_DPI = 1
MDT_RAW_DPI = 2
MDT_DEFAULT = 3
PROCESS_DPI_AWARENESS = UINT
PROCESS_DPI_UNAWARE = 0
PROCESS_SYSTEM_DPI_AWARE = 1
PROCESS_PER_MONITOR_DPI_AWARE = 2
DPI_AWARENESS_CONTEXT = HANDLE
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = DPI_AWARENESS_CONTEXT(-4)
MONITOR_DEFAULTTONEAREST = 0
MONITOR_DEFAULTTONULL = 1
MONITOR_DEFAULTTOPRIMARY = 2
def MAKEINTRESOURCE(i):
return cast(ctypes.c_void_p(i & 0xFFFF), c_wchar_p)

View File

@ -2613,13 +2613,6 @@ struct_anon_94._fields_ = [
('supported_values', POINTER(c_char_p)),
]
class XrmValue(Structure):
_fields_ = (
('size', c_uint),
('addr', c_char_p)
)
XIMValuesList = struct_anon_94 # /usr/include/X11/Xlib.h:1395
# /usr/include/X11/Xlib.h:1405
XLoadQueryFont = _lib.XLoadQueryFont
@ -2686,18 +2679,6 @@ XrmInitialize = _lib.XrmInitialize
XrmInitialize.restype = None
XrmInitialize.argtypes = []
XrmGetStringDatabase = _lib.XrmGetStringDatabase
XrmGetStringDatabase.restype = c_void_p
XrmGetStringDatabase.argtypes = [c_char_p]
XrmDestroyDatabase = _lib.XrmDestroyDatabase
XrmDestroyDatabase.restype = None
XrmDestroyDatabase.argtypes = [c_void_p]
XrmGetResource = _lib.XrmGetResource
XrmGetResource.restype = c_bool
XrmGetResource.argtypes = [c_void_p, c_char_p, c_char_p, POINTER(c_char_p), POINTER(XrmValue)]
# /usr/include/X11/Xlib.h:1502
XFetchBytes = _lib.XFetchBytes
XFetchBytes.restype = c_char_p

View File

@ -135,6 +135,17 @@ class AudioData:
self.duration -= num_bytes / audio_format.bytes_per_second
self.timestamp += num_bytes / audio_format.bytes_per_second
def get_string_data(self):
"""Return data as a bytestring.
Returns:
bytes: Data as a (byte)string.
"""
if self.data is None:
return b''
return memoryview(self.data).tobytes()[:self.length]
class SourceInfo:
"""Source metadata information.
@ -398,7 +409,7 @@ class StaticSource(Source):
audio_data = source.get_audio_data(buffer_size)
if not audio_data:
break
data.write(audio_data.data)
data.write(audio_data.get_string_data())
self._data = data.getvalue()
self._duration = len(self._data) / self.audio_format.bytes_per_second

View File

@ -15,18 +15,20 @@ for driver_name in pyglet.options['audio']:
_audio_driver = pulse.create_audio_driver()
break
elif driver_name == 'xaudio2':
from pyglet.libs.win32.constants import WINDOWS_8_OR_GREATER
if pyglet.compat_platform in ('win32', 'cygwin'):
from pyglet.libs.win32.constants import WINDOWS_8_OR_GREATER
if WINDOWS_8_OR_GREATER:
from . import xaudio2
if WINDOWS_8_OR_GREATER:
from . import xaudio2
_audio_driver = xaudio2.create_audio_driver()
break
_audio_driver = xaudio2.create_audio_driver()
break
elif driver_name == 'directsound':
from . import directsound
if pyglet.compat_platform in ('win32', 'cygwin'):
from . import directsound
_audio_driver = directsound.create_audio_driver()
break
_audio_driver = directsound.create_audio_driver()
break
elif driver_name == 'openal':
from . import openal
@ -60,48 +62,6 @@ def get_audio_driver():
AbstractAudioDriver : The concrete implementation of the preferred
audio driver for this platform.
"""
global _audio_driver
if _audio_driver:
return _audio_driver
_audio_driver = None
for driver_name in pyglet.options['audio']:
try:
if driver_name == 'pulse':
from . import pulse
_audio_driver = pulse.create_audio_driver()
break
elif driver_name == 'xaudio2':
if pyglet.compat_platform in ('win32', 'cygwin'):
from pyglet.libs.win32.constants import WINDOWS_8_OR_GREATER
if WINDOWS_8_OR_GREATER:
from . import xaudio2
_audio_driver = xaudio2.create_audio_driver()
break
elif driver_name == 'directsound':
if pyglet.compat_platform in ('win32', 'cygwin'):
from . import directsound
_audio_driver = directsound.create_audio_driver()
break
elif driver_name == 'openal':
from . import openal
_audio_driver = openal.create_audio_driver()
break
elif driver_name == 'silent':
from . import silent
_audio_driver = silent.create_audio_driver()
break
except Exception:
if _debug:
print(f'Error importing driver {driver_name}:')
import traceback
traceback.print_exc()
else:
from . import silent
_audio_driver = silent.create_audio_driver()
return _audio_driver

View File

@ -1,8 +1,9 @@
import math
import time
import weakref
from abc import ABCMeta, abstractmethod
import pyglet
from pyglet.util import with_metaclass
@ -184,3 +185,36 @@ class AbstractAudioDriver(with_metaclass(ABCMeta)):
@abstractmethod
def delete(self):
pass
class MediaEvent:
"""Representation of a media event.
These events are used internally by some audio driver implementation to
communicate events to the :class:`~pyglet.media.player.Player`.
One example is the ``on_eos`` event.
Args:
event (str): Event description.
timestamp (float): The time when this event happens.
*args: Any required positional argument to go along with this event.
"""
__slots__ = 'event', 'timestamp', 'args'
def __init__(self, event, timestamp=0, *args):
# Meaning of timestamp is dependent on context; and not seen by application.
self.event = event
self.timestamp = timestamp
self.args = args
def sync_dispatch_to_player(self, player):
pyglet.app.platform_event_loop.post_event(player, self.event, *self.args)
time.sleep(0)
# TODO sync with media.dispatch_events
def __repr__(self):
return f"MediaEvent({self.event}, {self.timestamp}, {self.args})"
def __lt__(self, other):
return hash(self) < hash(other)

View File

@ -1,11 +1,10 @@
import math
import ctypes
import pyglet.app
from . import interface
from pyglet.util import debug_print
from pyglet.media.mediathreads import PlayerWorkerThread
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer, MediaEvent
from pyglet.media.drivers.listener import AbstractListener
_debug = debug_print('debug_media')
@ -76,7 +75,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self._play_cursor_ring = 0
self._write_cursor_ring = 0
# List of play_cursor, in sort order
# List of (play_cursor, MediaEvent), in sort order
self._events = []
# List of (cursor, timestamp), in sort order (cursor gives expiry
@ -158,6 +157,9 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
return (self._eos_cursor is not None
and self._play_cursor > self._eos_cursor)
def _dispatch_new_event(self, event_name):
MediaEvent(event_name).sync_dispatch_to_player(self.player)
def _get_audiodata(self):
if self._audiodata_buffer is None or self._audiodata_buffer.length == 0:
self._get_new_audiodata()
@ -244,7 +246,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
if self._playing and self._has_underrun():
assert _debug('underrun, stopping')
self.stop()
pyglet.app.platform_event_loop.post_event(self.player, 'on_eos')
self._dispatch_new_event('on_eos')
def _get_write_size(self):
self.update_play_cursor()

View File

@ -1,9 +1,8 @@
import weakref
import pyglet.app
from . import interface
from pyglet.util import debug_print
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer, MediaEvent
from pyglet.media.mediathreads import PlayerWorkerThread
from pyglet.media.drivers.listener import AbstractListener
@ -112,7 +111,7 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
# of underrun)
self._underrun_timestamp = None
# List of cursor
# List of (cursor, MediaEvent)
self._events = []
# Desired play state (True even if stopped due to underrun)
@ -281,7 +280,7 @@ class OpenALAudioPlayer(AbstractAudioPlayer):
assert _debug('No audio data left')
if self._has_underrun():
assert _debug('Underrun')
pyglet.app.platform_event_loop.post_event(self.player, 'on_eos')
MediaEvent('on_eos').sync_dispatch_to_player(self.player)
def _queue_audio_data(self, audio_data, length):
buf = self.alsource.get_buffer()

View File

@ -1,7 +1,6 @@
import weakref
import pyglet.app
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer, MediaEvent
from pyglet.media.drivers.listener import AbstractListener
from pyglet.util import debug_print
@ -218,7 +217,7 @@ class PulseAudioPlayer(AbstractAudioPlayer):
if self._has_audio_data():
self._write_to_stream()
else:
self._events.append('on_eos')
self._add_event_at_write_index('on_eos')
def _process_events(self):
assert _debug('PulseAudioPlayer: Process events')
@ -236,9 +235,13 @@ class PulseAudioPlayer(AbstractAudioPlayer):
assert _debug('PulseAudioPlayer: Dispatch events at index {}'.format(read_index))
while self._events and self._events[0][0] <= read_index:
event_name = self._events.pop(0)
assert _debug('PulseAudioPlayer: Dispatch event', event_name)
pyglet.app.platform_event_loop.post_event(self.player, event_name)
_, event = self._events.pop(0)
assert _debug('PulseAudioPlayer: Dispatch event', event)
event._sync_dispatch_to_player(self.player)
def _add_event_at_write_index(self, event_name):
assert _debug('PulseAudioPlayer: Add event at index {}'.format(self._write_index))
self._events.append((self._write_index, MediaEvent(event_name)))
def delete(self):
assert _debug('Delete PulseAudioPlayer')
@ -252,7 +255,7 @@ class PulseAudioPlayer(AbstractAudioPlayer):
if driver.mainloop is None:
assert _debug('PulseAudioDriver already deleted. '
'PulseAudioPlayer could not clean up properly.')
'PulseAudioPlayer could not clean up properly.')
return
if self._time_sync_operation is not None:

View File

@ -1,7 +1,7 @@
import math
import pyglet
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer, MediaEvent
from pyglet.media.drivers.listener import AbstractListener
from pyglet.util import debug_print
from . import interface
@ -48,7 +48,7 @@ class XAudio2AudioPlayer(AbstractAudioPlayer):
self._write_cursor = 0
self._play_cursor = 0
# List of play_cursor, in sort order
# List of (play_cursor, MediaEvent), in sort order
self._events = []
# List of (cursor, timestamp), in sort order (cursor gives expiry
@ -89,6 +89,7 @@ class XAudio2AudioPlayer(AbstractAudioPlayer):
if not self._buffers:
self._xa2_driver.return_voice(self._xa2_source_voice)
def play(self):
assert _debug('XAudio2 play')
@ -167,7 +168,7 @@ class XAudio2AudioPlayer(AbstractAudioPlayer):
if self.buffer_end_submitted:
if buffers_queued == 0:
self._xa2_source_voice.stop()
pyglet.app.platform_event_loop.post_event(self.player, 'on_eos')
MediaEvent("on_eos").sync_dispatch_to_player(self.player)
else:
current_buffers = []
while buffers_queued < self.max_buffer_count:
@ -205,6 +206,9 @@ class XAudio2AudioPlayer(AbstractAudioPlayer):
self._dispatch_pending_events()
def _dispatch_new_event(self, event_name):
MediaEvent(event_name).sync_dispatch_to_player(self.player)
def _add_audiodata_events(self, audio_data):
for event in audio_data.events:
event_cursor = self._write_cursor + event.timestamp * self.source.audio_format.bytes_per_second

View File

@ -215,7 +215,7 @@ class BaseMaterialGroup(graphics.Group):
class TexturedMaterialGroup(BaseMaterialGroup):
default_vert_src = """#version 330 core
in vec3 vertices;
in vec3 position;
in vec3 normals;
in vec2 tex_coords;
in vec4 colors;
@ -235,7 +235,7 @@ class TexturedMaterialGroup(BaseMaterialGroup):
void main()
{
vec4 pos = window.view * model * vec4(vertices, 1.0);
vec4 pos = window.view * model * vec4(position, 1.0);
gl_Position = window.projection * pos;
mat3 normal_matrix = transpose(inverse(mat3(model)));
@ -286,7 +286,7 @@ class TexturedMaterialGroup(BaseMaterialGroup):
class MaterialGroup(BaseMaterialGroup):
default_vert_src = """#version 330 core
in vec3 vertices;
in vec3 position;
in vec3 normals;
in vec4 colors;
@ -304,7 +304,7 @@ class MaterialGroup(BaseMaterialGroup):
void main()
{
vec4 pos = window.view * model * vec4(vertices, 1.0);
vec4 pos = window.view * model * vec4(position, 1.0);
gl_Position = window.projection * pos;
mat3 normal_matrix = transpose(inverse(mat3(model)));

View File

@ -214,7 +214,7 @@ class OBJModelDecoder(ModelDecoder):
texture = pyglet.resource.texture(material.texture_name)
matgroup = TexturedMaterialGroup(material, program, texture, parent=group)
vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, matgroup,
vertices=('f', mesh.vertices),
position=('f', mesh.vertices),
normals=('f', mesh.normals),
tex_coords=('f', mesh.tex_coords),
colors=('f', material.diffuse * count)))
@ -222,7 +222,7 @@ class OBJModelDecoder(ModelDecoder):
program = pyglet.model.get_default_shader()
matgroup = MaterialGroup(material, program, parent=group)
vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, matgroup,
vertices=('f', mesh.vertices),
position=('f', mesh.vertices),
normals=('f', mesh.normals),
colors=('f', material.diffuse * count)))
groups.append(matgroup)

View File

@ -56,7 +56,7 @@ from pyglet.graphics import Batch, Group
vertex_source = """#version 150 core
in vec2 vertices;
in vec2 position;
in vec2 translation;
in vec4 colors;
in float rotation;
@ -82,7 +82,7 @@ vertex_source = """#version 150 core
m_rotation[1][0] = -sin(-radians(rotation));
m_rotation[1][1] = cos(-radians(rotation));
gl_Position = window.projection * window.view * m_translate * m_rotation * vec4(vertices, 0.0, 1.0);
gl_Position = window.projection * window.view * m_translate * m_rotation * vec4(position, 0.0, 1.0);
vertex_colors = colors;
}
"""
@ -181,6 +181,7 @@ class ShapeBase(ABC):
_num_verts = 0
_vertex_list = None
_draw_mode = GL_TRIANGLES
group_class = _ShapeGroup
def __del__(self):
if self._vertex_list is not None:
@ -394,7 +395,7 @@ class ShapeBase(ABC):
def group(self, group):
if self._group.parent == group:
return
self._group = _ShapeGroup(self._group.blend_src,
self._group = self.group_class(self._group.blend_src,
self._group.blend_dest,
self._group.program,
group)
@ -476,7 +477,7 @@ class Arc(ShapeBase):
self._batch = batch or Batch()
program = get_default_shader()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -511,7 +512,7 @@ class Arc(ShapeBase):
chord_points = *points[-1], *points[0]
vertices.extend(chord_points)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def rotation(self):
@ -599,7 +600,7 @@ class BezierCurve(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -640,7 +641,7 @@ class BezierCurve(ShapeBase):
line_points = *coords[i], *coords[i + 1]
vertices.extend(line_points)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def points(self):
@ -707,7 +708,7 @@ class Circle(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -737,7 +738,7 @@ class Circle(ShapeBase):
triangle = x, y, *points[i - 1], *point
vertices.extend(triangle)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def radius(self):
@ -796,7 +797,7 @@ class Ellipse(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -825,7 +826,7 @@ class Ellipse(ShapeBase):
line_points = *points[i], *points[i + 1]
vertices.extend(line_points)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def a(self):
@ -918,7 +919,7 @@ class Sector(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -949,7 +950,7 @@ class Sector(ShapeBase):
triangle = x, y, *points[i - 1], *point
vertices.extend(triangle)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def angle(self):
@ -1049,7 +1050,7 @@ class Line(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1062,7 +1063,7 @@ class Line(ShapeBase):
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
else:
x1 = -self._anchor_x
y1 = self._anchor_y - self._width / 2
@ -1081,7 +1082,7 @@ class Line(ShapeBase):
dx = x1 * cr - y2 * sr
dy = x1 * sr + y2 * cr
self._vertex_list.vertices[:] = (ax, ay, bx, by, cx, cy, ax, ay, cx, cy, dx, dy)
self._vertex_list.position[:] = (ax, ay, bx, by, cx, cy, ax, ay, cx, cy, dx, dy)
@property
def x2(self):
@ -1148,7 +1149,7 @@ class Rectangle(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1161,14 +1162,14 @@ class Rectangle(ShapeBase):
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
else:
x1 = -self._anchor_x
y1 = -self._anchor_y
x2 = x1 + self._width
y2 = y1 + self._height
self._vertex_list.vertices[:] = x1, y1, x2, y1, x2, y2, x1, y1, x2, y2, x1, y2
self._vertex_list.position[:] = x1, y1, x2, y1, x2, y2, x1, y1, x2, y2, x1, y2
@property
def width(self):
@ -1282,7 +1283,7 @@ class BorderedRectangle(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1299,7 +1300,7 @@ class BorderedRectangle(ShapeBase):
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
else:
bx1 = -self._anchor_x
by1 = -self._anchor_y
@ -1311,7 +1312,7 @@ class BorderedRectangle(ShapeBase):
ix2 = bx2 - b
iy2 = by2 - b
self._vertex_list.vertices[:] = (ix1, iy1, ix2, iy1, ix2, iy2, ix1, iy2,
self._vertex_list.position[:] = (ix1, iy1, ix2, iy1, ix2, iy2, ix1, iy2,
bx1, by1, bx2, by1, bx2, by2, bx1, by2)
@property
@ -1461,7 +1462,7 @@ class Triangle(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1474,7 +1475,7 @@ class Triangle(ShapeBase):
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0)
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0)
else:
x1 = -self._anchor_x
y1 = -self._anchor_y
@ -1482,7 +1483,7 @@ class Triangle(ShapeBase):
y2 = self._y2 + y1 - self._y
x3 = self._x3 + x1 - self._x
y3 = self._y3 + y1 - self._y
self._vertex_list.vertices[:] = (x1, y1, x2, y2, x3, y3)
self._vertex_list.position[:] = (x1, y1, x2, y2, x3, y3)
@property
def x2(self):
@ -1581,7 +1582,7 @@ class Star(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1619,7 +1620,7 @@ class Star(ShapeBase):
triangle = x, y, *points[i - 1], *point
vertices.extend(triangle)
self._vertex_list.vertices[:] = vertices
self._vertex_list.position[:] = vertices
@property
def outer_radius(self):
@ -1692,7 +1693,7 @@ class Polygon(ShapeBase):
program = get_default_shader()
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._group = self.group_class(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._create_vertex_list()
self._update_vertices()
@ -1706,7 +1707,7 @@ class Polygon(ShapeBase):
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = tuple([0] * ((len(self._coordinates) - 2) * 6))
self._vertex_list.position[:] = tuple([0] * ((len(self._coordinates) - 2) * 6))
else:
# Adjust all coordinates by the anchor.
trans_x, trans_y = self._coordinates[0]
@ -1720,7 +1721,7 @@ class Polygon(ShapeBase):
triangles += [coords[0], coords[n + 1], coords[n + 2]]
# Flattening the list before setting vertices to it.
self._vertex_list.vertices[:] = tuple(value for coordinate in triangles for value in coordinate)
self._vertex_list.position[:] = tuple(value for coordinate in triangles for value in coordinate)
@property
def rotation(self):

View File

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

View File

@ -989,7 +989,6 @@ class TextLayout:
self._document = document
self._init_document()
@property
def batch(self):
"""The Batch that this Layout is assigned to.
@ -1326,21 +1325,6 @@ class TextLayout:
self._update_enabled = True
self._update()
@property
def dpi(self):
"""Get DPI used by this layout.
Read-only.
:type: float
"""
return self._dpi
@dpi.setter
def dpi(self, value):
self._dpi = value
self._update()
def delete(self):
"""Remove this layout from its batch.
"""

View File

@ -98,7 +98,7 @@ import pyglet.window.mouse
from pyglet import gl
from pyglet.math import Mat4
from pyglet.event import EventDispatcher
from pyglet.window import key
from pyglet.window import key, event
from pyglet.util import with_metaclass
from pyglet.graphics import shader
@ -367,7 +367,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
# Instance variables accessible only via properties
_width = None
_height = None
_dpi = 96
_caption = None
_resizable = False
_style = WINDOW_STYLE_DEFAULT
@ -385,9 +384,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
# Used to restore window size and position after fullscreen
_windowed_size = None
_windowed_location = None
# Used to tell if window is currently being resized.
_window_resizing = False
_minimum_size = None
_maximum_size = None
@ -929,13 +925,29 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
height = self.screen.height
return width, height
def on_scale(self, scale, dpi):
"""A default scale event handler.
def set_minimum_size(self, width: int, height: int) -> None:
"""Set the minimum size of the window.
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).
The behaviour is undefined if the minimum size is set larger than
the current size of the window.
The window size does not include the border or title bar.
:Parameters:
`width` : int
Minimum width of the window, in pixels.
`height` : int
Minimum height of the window, in pixels.
This default handler is called if the screen or system's DPI changes
during runtime.
"""
pass
if width < 1 or height < 1:
raise ValueError('width and height must be positive integers')
self._minimum_size = width, height
def set_maximum_size(self, width: int, height: int) -> None:
"""Set the maximum size of the window.
@ -1247,22 +1259,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
def height(self, new_height):
self.set_size(self.width, new_height)
@property
def scale(self):
"""The scale of the window factoring in DPI. Read only.
:type: float
"""
return self._dpi / 96
@property
def dpi(self):
"""DPI values of the Window. Read only.
:type: list
"""
return self._dpi
@property
def size(self):
"""The size of the window. Read-Write.
@ -1270,7 +1266,7 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:type: tuple
"""
return self.get_size()
@size.setter
def size(self, new_size):
self.set_size(*new_size)
@ -1352,30 +1348,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
x, y, w, h = values
pyglet.gl.glViewport(int(x * pr), int(y * pr), int(w * pr), int(h * pr))
def set_minimum_size(self, width: int, height: int) -> None:
"""Set the minimum size of the window.
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 1, 1).
The behaviour is undefined if the minimum size is set larger than
the current size of the window.
The window size does not include the border or title bar.
:Parameters:
`width` : int
Minimum width of the window, in pixels.
`height` : int
Minimum height of the window, in pixels.
"""
if width < 1 or height < 1:
raise ValueError('width and height must be positive integers')
self._minimum_size = width, height
# If documenting, show the event methods. Otherwise, leave them out
# as they are not really methods.
if _is_pyglet_doc_run:
@ -1692,20 +1664,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_resize_stop(self, width, height):
"""The window is done being resized.
Called when the window is done resizing.
:Parameters:
`width` : int
The new width of the window, in pixels.
`height` : int
The new height of the window, in pixels.
:event:
"""
def on_show(self):
"""The window was shown.
@ -1822,8 +1780,6 @@ BaseWindow.register_event_type('on_mouse_leave')
BaseWindow.register_event_type('on_close')
BaseWindow.register_event_type('on_expose')
BaseWindow.register_event_type('on_resize')
BaseWindow.register_event_type('on_resize_stop')
BaseWindow.register_event_type('on_scale')
BaseWindow.register_event_type('on_move')
BaseWindow.register_event_type('on_activate')
BaseWindow.register_event_type('on_deactivate')

View File

@ -8,7 +8,7 @@ from pyglet.event import EventDispatcher
from pyglet.canvas.cocoa import CocoaCanvas
from pyglet.libs.darwin import cocoapy, CGPoint, NSDeviceResolution
from pyglet.libs.darwin import cocoapy, CGPoint, AutoReleasePool
from .systemcursor import SystemCursor
from .pyglet_delegate import PygletDelegate
@ -155,11 +155,9 @@ class CocoaWindow(BaseWindow):
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._nsview.setWantsBestResolutionOpenGLSurface_(1 if pyglet.options["scale_with_dpi"] else 0)
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)
# Configure the window.
self._nswindow.setAcceptsMouseMovedEvents_(True)
@ -181,38 +179,10 @@ class CocoaWindow(BaseWindow):
array = NSArray.arrayWithObject_(cocoapy.NSPasteboardTypeURL)
self._nsview.registerForDraggedTypes_(array)
self._dpi = self._get_dpi_desc()
# TODO: Add support for file drops.
if self._file_drops:
raise NotImplementedError("File drops are not implemented on MacOS")
self.context.update_geometry()
self.switch_to()
self.set_vsync(self._vsync)
self.set_visible(self._visible)
pool.drain()
def _get_dpi_desc(self):
if pyglet.options["scale_with_dpi"] and self._nswindow:
desc = self._nswindow.deviceDescription()
rsize = desc.objectForKey_(NSDeviceResolution).sizeValue()
dpi = int(rsize.width)
return dpi
return 72
@property
def scale(self):
"""The scale of the window factoring in DPI. Read only.
:type: float
"""
if pyglet.options["scale_with_dpi"] and self._nswindow:
return self._nswindow.backingScaleFactor()
return 1.0
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.
@ -389,10 +359,8 @@ class CocoaWindow(BaseWindow):
def get_framebuffer_size(self):
view = self.context._nscontext.view()
bounds = view.bounds()
if pyglet.options["scale_with_dpi"]:
bounds = view.convertRectToBacking_(bounds)
return int(bounds.size.width), int(bounds.size.height)
bounds = view.convertRectToBacking_(view.bounds()).size
return int(bounds.width), int(bounds.height)
def set_size(self, width: int, height: int) -> None:
super().set_size(width, height)

View File

@ -3,13 +3,12 @@ from pyglet.libs.darwin.cocoapy import NSApplicationDidHideNotification
from pyglet.libs.darwin.cocoapy import NSApplicationDidUnhideNotification
from pyglet.libs.darwin.cocoapy import send_super, get_selector
from pyglet.libs.darwin.cocoapy import PyObjectEncoding
from pyglet.libs.darwin.cocoapy import quartz, appkit
from pyglet.libs.darwin.cocoapy import quartz
from .systemcursor import SystemCursor
NSNotificationCenter = ObjCClass('NSNotificationCenter')
NSApplication = ObjCClass('NSApplication')
NSBackingPropertyOldScaleFactorKey = c_void_p.in_dll(appkit, 'NSBackingPropertyOldScaleFactorKey')
class PygletDelegate_Implementation:
PygletDelegate = ObjCSubclass('NSObject', 'PygletDelegate')
@ -129,14 +128,5 @@ class PygletDelegate_Implementation:
return not self._window._keyboard_exclusive
return True
@PygletDelegate.method('v@')
def windowDidChangeBackingProperties_(self, notification):
user_info = notification.userInfo()
old_scale = user_info.objectForKey_(NSBackingPropertyOldScaleFactorKey)
new_scale = self._window._nswindow.backingScaleFactor()
if old_scale.doubleValue() != new_scale:
self._window.dispatch_event("on_scale", new_scale, self._window._get_dpi_desc())
PygletDelegate = ObjCClass('PygletDelegate')

View File

@ -1,7 +1,9 @@
from pyglet.window import key, mouse
from pyglet.libs.darwin.quartzkey import keymap, charmap
from pyglet.libs.darwin import cocoapy, NSPasteboardURLReadingFileURLsOnlyKey
from pyglet.libs.darwin import cocoapy, NSPasteboardURLReadingFileURLsOnlyKey, NSLeftShiftKeyMask, NSRightShiftKeyMask, \
NSLeftControlKeyMask, NSRightControlKeyMask, NSLeftAlternateKeyMask, NSRightAlternateKeyMask, NSLeftCommandKeyMask, \
NSRightCommandKeyMask, NSFunctionKeyMask, NSAlphaShiftKeyMask
from .pyglet_textview import PygletTextView
@ -11,6 +13,20 @@ NSArray = cocoapy.ObjCClass('NSArray')
NSDictionary = cocoapy.ObjCClass('NSDictionary')
NSNumber = cocoapy.ObjCClass('NSNumber')
# Key to mask mapping.
maskForKey = {
key.LSHIFT: NSLeftShiftKeyMask,
key.RSHIFT: NSRightShiftKeyMask,
key.LCTRL: NSLeftControlKeyMask,
key.RCTRL: NSRightControlKeyMask,
key.LOPTION: NSLeftAlternateKeyMask,
key.ROPTION: NSRightAlternateKeyMask,
key.LCOMMAND: NSLeftCommandKeyMask,
key.RCOMMAND: NSRightCommandKeyMask,
key.CAPSLOCK: NSAlphaShiftKeyMask,
key.FUNCTION: NSFunctionKeyMask
}
# Event data helper functions.
@ -123,6 +139,10 @@ class PygletView_Implementation:
self.addTrackingArea_(self._tracking_area)
@PygletView.method('B')
def acceptsFirstResponder (self):
return True
@PygletView.method('B')
def canBecomeKeyView(self):
return True
@ -164,45 +184,23 @@ class PygletView_Implementation:
app.event_loop.idle()
@PygletView.method('v@')
def pygletKeyDown_(self, nsevent):
symbol = getSymbol(nsevent)
modifiers = getModifiers(nsevent)
self._window.dispatch_event('on_key_press', symbol, modifiers)
def keyDown_(self, nsevent):
if not nsevent.isARepeat():
symbol = getSymbol(nsevent)
modifiers = getModifiers(nsevent)
self._window.dispatch_event('on_key_press', symbol, modifiers)
@PygletView.method('v@')
def pygletKeyUp_(self, nsevent):
def keyUp_(self, nsevent):
symbol = getSymbol(nsevent)
modifiers = getModifiers(nsevent)
self._window.dispatch_event('on_key_release', symbol, modifiers)
@PygletView.method('v@')
def pygletFlagsChanged_(self, nsevent):
def flagsChanged_(self, nsevent):
# Handles on_key_press and on_key_release events for modifier keys.
# Note that capslock is handled differently than other keys; it acts
# as a toggle, so on_key_release is only sent when it's turned off.
# TODO: Move these constants somewhere else.
# Undocumented left/right modifier masks found by experimentation:
NSLeftShiftKeyMask = 1 << 1
NSRightShiftKeyMask = 1 << 2
NSLeftControlKeyMask = 1 << 0
NSRightControlKeyMask = 1 << 13
NSLeftAlternateKeyMask = 1 << 5
NSRightAlternateKeyMask = 1 << 6
NSLeftCommandKeyMask = 1 << 3
NSRightCommandKeyMask = 1 << 4
maskForKey = {key.LSHIFT: NSLeftShiftKeyMask,
key.RSHIFT: NSRightShiftKeyMask,
key.LCTRL: NSLeftControlKeyMask,
key.RCTRL: NSRightControlKeyMask,
key.LOPTION: NSLeftAlternateKeyMask,
key.ROPTION: NSRightAlternateKeyMask,
key.LCOMMAND: NSLeftCommandKeyMask,
key.RCOMMAND: NSRightCommandKeyMask,
key.CAPSLOCK: cocoapy.NSAlphaShiftKeyMask,
key.FUNCTION: cocoapy.NSFunctionKeyMask}
symbol = keymap.get(nsevent.keyCode(), None)
# Ignore this event if symbol is not a modifier key. We must check this

View File

@ -100,7 +100,6 @@ class Win32Window(BaseWindow):
self._always_dwm = sys.getwindowsversion() >= (6, 2)
self._interval = 0
self._timer = None
super(Win32Window, self).__init__(*args, **kwargs)
@ -135,19 +134,12 @@ class Win32Window(BaseWindow):
else:
self._ws_style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX)
self._dpi = self._screen.get_dpi()
if self._fullscreen:
width = self.screen.width
height = self.screen.height
else:
if pyglet.options["scale_with_dpi"]:
if self.scale != 1.0:
self._width = int(self._width * self.scale)
self._height = int(self._height * self.scale)
width, height = \
self._client_to_window_size(self._width, self._height, self._dpi)
self._client_to_window_size(self._width, self._height)
if not self._window_class:
module = _kernel32.GetModuleHandleW(None)
@ -375,12 +367,11 @@ class Win32Window(BaseWindow):
return point.x, point.y
def set_size(self, width, height):
if self._fullscreen:
raise WindowException('Cannot set size of fullscreen window.')
width, height = self._client_to_window_size_dpi(width, height)
super().set_size(width, height)
width, height = self._client_to_window_size(width, height)
_user32.SetWindowPos(self._hwnd, 0, 0, 0, width, height,
(SWP_NOZORDER | SWP_NOMOVE | SWP_NOOWNERZORDER))
self.dispatch_event('on_resize', width, height)
self.dispatch_event('on_resize', self._width, self._height)
def get_size(self):
# rect = RECT()
@ -417,13 +408,6 @@ class Win32Window(BaseWindow):
def maximize(self):
_user32.ShowWindow(self._hwnd, SW_MAXIMIZE)
def get_window_screen(self):
""" Gets the current screen the window is on.
If between monitors will retrieve the screen with the most screen space.
"""
handle = _user32.MonitorFromWindow(self._hwnd, MONITOR_DEFAULTTONEAREST)
return [screen for screen in self.display.get_screens() if screen._handle == handle][0]
def set_caption(self, caption):
self._caption = caption
_user32.SetWindowTextW(self._hwnd, c_wchar_p(caption))
@ -670,41 +654,15 @@ class Win32Window(BaseWindow):
return icon
# Private util
def _client_to_window_size(self, width, height, dpi):
"""This returns the true window size factoring in styles, borders, title bars"""
def _client_to_window_size(self, width, height):
rect = RECT()
rect.left = 0
rect.top = 0
rect.right = width
rect.bottom = height
if WINDOWS_10_ANNIVERSARY_UPDATE_OR_GREATER:
_user32.AdjustWindowRectExForDpi(byref(rect),
self._ws_style, False, self._ex_ws_style, dpi)
else:
_user32.AdjustWindowRectEx(byref(rect),
self._ws_style, False, self._ex_ws_style)
return rect.right - rect.left, rect.bottom - rect.top
def _client_to_window_size_dpi(self, width, height):
""" This returns the true window size factoring in styles, borders, title bars.
Retrieves DPI directly from the Window hwnd, used after window creation.
"""
rect = RECT()
rect.left = 0
rect.top = 0
rect.right = width
rect.bottom = height
if WINDOWS_10_ANNIVERSARY_UPDATE_OR_GREATER:
_user32.AdjustWindowRectExForDpi(byref(rect),
self._ws_style, False, self._ex_ws_style, _user32.GetDpiForWindow(self._hwnd))
else:
_user32.AdjustWindowRectEx(byref(rect),
self._ws_style, False, self._ex_ws_style)
_user32.AdjustWindowRectEx(byref(rect),
self._ws_style, False, self._ex_ws_style)
return rect.right - rect.left, rect.bottom - rect.top
def _client_to_window_pos(self, x, y):
@ -712,14 +670,7 @@ class Win32Window(BaseWindow):
rect.left = x
rect.top = y
_user32.AdjustWindowRectEx(byref(rect),
self._ws_style, False, self._ex_ws_style)
if WINDOWS_10_ANNIVERSARY_UPDATE_OR_GREATER:
_user32.AdjustWindowRectExForDpi(byref(rect),
self._ws_style, False, self._ex_ws_style, _user32.GetDpiForWindow(self._hwnd))
else:
_user32.AdjustWindowRectEx(byref(rect),
self._ws_style, False, self._ex_ws_style)
self._ws_style, False, self._ex_ws_style)
return rect.left, rect.top
# Event dispatching
@ -1100,7 +1051,6 @@ class Win32Window(BaseWindow):
def _event_sizing(self, msg, wParam, lParam):
# rect = cast(lParam, POINTER(RECT)).contents
# width, height = self.get_size()
self._window_resizing = True
from pyglet import app
if app.event_loop is not None:
@ -1149,54 +1099,39 @@ class Win32Window(BaseWindow):
if app.event_loop is not None:
app.event_loop.enter_blocking()
@Win32EventHandler(WM_TIMER)
def _event_timer(self, msg, wParam, lParam):
"""This allows events and rendering to occur even when the window is moving."""
if not self._window_resizing:
from pyglet import app
if app.event_loop is not None:
app.event_loop.idle()
@Win32EventHandler(WM_MOVE)
def _event_move(self, msg, wParam, lParam):
x, y = self._get_location(lParam)
self.dispatch_event('on_move', x, y)
return 0
@Win32EventHandler(WM_SETCURSOR)
def _event_setcursor(self, msg, wParam, lParam):
if self._exclusive_mouse and not self._mouse_platform_visible:
lo, hi = self._get_location(lParam)
if lo == HTCLIENT: # In frame
self._set_cursor_visibility(False)
return 1
elif lo in (HTCAPTION, HTCLOSE, HTMAXBUTTON, HTMINBUTTON): # Allow in
self._set_cursor_visibility(True)
return 1
@Win32EventHandler(WM_ENTERSIZEMOVE)
def _event_entersizemove(self, msg, wParam, lParam):
self._timer = _user32.SetTimer(self._hwnd, 100, 25, None)
@Win32EventHandler(WM_EXITSIZEMOVE)
def _event_exitsizemove(self, msg, wParam, lParam):
self._moving = True
from pyglet import app
if self._timer:
_user32.KillTimer(self._hwnd, 100)
self._timer = None
if app.event_loop is not None:
app.event_loop.exit_blocking()
# _window_resizing is needed in combination with WM_EXITSIZEMOVE as
# it is also called when the Window is done being moved, not just resized.
if self._window_resizing is True:
self.dispatch_event('on_resize_stop', self._width, self._height)
self._window_resizing = False
@Win32EventHandler(WM_EXITSIZEMOVE)
def _event_exitsizemove(self, msg, wParam, lParam):
self._moving = False
from pyglet import app
if app.event_loop is not None:
app.event_loop.exit_blocking()
# Alternative to using WM_SETFOCUS and WM_KILLFOCUS. Which
# is better?
"""
@Win32EventHandler(WM_ACTIVATE)
def _event_activate(self, msg, wParam, lParam):
if wParam & 0xffff == WA_INACTIVE:
self.dispatch_event('on_deactivate')
else:
self.dispatch_event('on_activate')
_user32.SetFocus(self._hwnd)
return 0
"""
if self._exclusive_mouse:
self._update_clipped_cursor()
@Win32EventHandler(WM_SETFOCUS)
def _event_setfocus(self, msg, wParam, lParam):
@ -1237,15 +1172,15 @@ class Win32Window(BaseWindow):
@Win32EventHandler(WM_GETMINMAXINFO)
def _event_getminmaxinfo(self, msg, wParam, lParam):
"""Used to determine the minimum or maximum sized window if configured."""
info = MINMAXINFO.from_address(lParam)
if self._minimum_size:
info.ptMinTrackSize.x, info.ptMinTrackSize.y = \
self._client_to_window_size_dpi(*self._minimum_size)
self._client_to_window_size(*self._minimum_size)
if self._maximum_size:
info.ptMaxTrackSize.x, info.ptMaxTrackSize.y = \
self._client_to_window_size_dpi(*self._maximum_size)
self._client_to_window_size(*self._maximum_size)
return 0
@Win32EventHandler(WM_ERASEBKGND)
@ -1289,51 +1224,5 @@ class Win32Window(BaseWindow):
self.dispatch_event('on_file_drop', point.x, self._height - point.y, paths)
return 0
@Win32EventHandler(WM_GETDPISCALEDSIZE)
def _event_dpi_scaled_size(self, msg, wParam, lParam):
if pyglet.options["scale_with_dpi"]:
return None
size = cast(lParam, POINTER(SIZE)).contents
dpi = wParam
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
current = RECT()
result = RECT()
# Size between current size and future.
_user32.AdjustWindowRectExForDpi(byref(current),
self._ws_style, False, self._ex_ws_style,
_user32.GetDpiForWindow(self._hwnd))
_user32.AdjustWindowRectExForDpi(byref(result),
self._ws_style, False, self._ex_ws_style, dpi)
size.cx += (result.right - result.left) - (current.right - current.left)
size.cy += (result.bottom - result.top) - (current.bottom - current.top)
return 1
@Win32EventHandler(WM_DPICHANGED)
def _event_dpi_change(self, msg, wParam, lParam):
y_dpi, x_dpi = self._get_location(wParam)
scale = x_dpi / USER_DEFAULT_SCREEN_DPI
if not self._fullscreen and\
(pyglet.options["scale_with_dpi"] or WINDOWS_10_CREATORS_UPDATE_OR_GREATER):
suggested_rect = cast(lParam, POINTER(RECT)).contents
x = suggested_rect.left
y = suggested_rect.top
width = suggested_rect.right - suggested_rect.left
height = suggested_rect.bottom - suggested_rect.top
_user32.SetWindowPos(self._hwnd, 0,
x, y, width, height, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE)
self._dpi = x_dpi
self.switch_to()
self.dispatch_event('on_scale', scale, x_dpi)
return 1
__all__ = ["Win32EventHandler", "Win32Window"]

View File

@ -214,7 +214,10 @@ class XlibWindow(BaseWindow):
# unconditionally.
mask = xlib.CWColormap | xlib.CWBitGravity | xlib.CWBackPixel
self._dpi = self._screen.get_dpi()
if self.style in ('transparent', 'overlay'):
mask |= xlib.CWBorderPixel
window_attributes.border_pixel = 0
window_attributes.background_pixel = 0
if self._fullscreen:
width, height = self.screen.width, self.screen.height
@ -222,11 +225,6 @@ class XlibWindow(BaseWindow):
self._view_y = (height - self._height) // 2
else:
width, height = self._width, self._height
if pyglet.options["scale_with_dpi"]:
if self.scale != 1.0:
self._width = width = int(self._width * self.scale)
self._height = height = int(self._height * self.scale)
self._view_x = self._view_y = 0
self._window = xlib.XCreateWindow(self._x_display, root,