pyglet fetch up
This commit is contained in:
parent
43532f8bec
commit
96b270b096
@ -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')
|
||||
|
@ -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})"
|
@ -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::
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
||||
|
179
libs/pyglet/image/animation.py
Normal file
179
libs/pyglet/image/animation.py
Normal 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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user