use new pyglet local

[build skip] docs

try hard link

非得更新一下是吧 [build skip]
This commit is contained in:
shenjack 2023-02-23 22:08:53 +08:00
parent 46f21e607a
commit f16807715c
16 changed files with 903 additions and 370 deletions

View File

@ -1,7 +1,5 @@
# Difficult Rocket
[comment]: <> ([中文]&#40;./docs/README-cn.md&#41; | English)
中文 | [English](/docs/README-en.md)
- [GitHub](https://github.com/shenjackyuanjie/Difficult-Rocket)

View File

@ -1,7 +1,5 @@
# Difficult Rocket
[comment]: <> (中文 | [English]&#40;https://github.com/shenjackyuanjie/Difficult-Rocket&#41;.)
[中文](../README.md) | English
- [GitHub](https://github.com/shenjackyuanjie/Difficult-Rocket)

View File

@ -1,4 +1,4 @@
# 嘿 看啥呢
# 嘿 看啥呢 404!
- 很明显,这里没有你想要的东西

116
docs/src/README-en.md Normal file
View File

@ -0,0 +1,116 @@
# Difficult Rocket
[中文](../README.md) | English
- [GitHub](https://github.com/shenjackyuanjie/Difficult-Rocket)
- [gitee](https://gitee.com/shenjackyuanjie/Difficult-Rocket)
- [discord](https://discord.gg/kWzw2JrG6M)
- [kook](https://kook.top/sRPjFG)
<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg" alt="996.icu" /></a>
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
[![Generic badge](https://img.shields.io/badge/Write_with_Python-3.8.10-blue.svg)](https://Python.org)
[![Generic badge](https://img.shields.io/badge/Write_with_Pyglet-2.0.4-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_|_3.11_-blue.svg)](https://Python.org)
## Version
[![Generic badge](https://img.shields.io/badge/Release-0.7.1.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.7.1.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
## 中文README请移步 [这里](../README.md)
> Difficult-rocket is a Simple Rocket liked game build with Python (in short: rocket simulator)
## Advantage
> Lighter than Vanilla SR
## [Plan feature list](/docs/src/plan_features.md)
- [microsoft TODO](https://to-do.microsoft.com/sharing?InvitationToken=Q6SN1kdtitK8cwFktFl71gSnsRMNmrH7CC7kHY_Tq6ReMRwHgInP4_q5ie2IwrHx8)
## [Update logs](/docs/src/update_logs.md)
## Environment (been tested / developed on)
- `Develop platform 1 - Windows 10 x64 22H2`
- `Python 3.8.10`
- `pillow 9.3.0`
- `pyperclip 1.8.2`
- `pyglet 2.0`
- `psutil 5.9.4`
- `objprint 0.2.2`
- `rtoml 0.9.0`
- `xmltodict 0.13.0`
- `tomlkit 0.11.6`
- `AMD R5 5600X`
- `AMD RX 550 4G`
## Required python modules
- `rtoml`
- `tomlkit`
- `pyglet` (pre-installed V2.0.4 path:`./libs/pyglet`)
- `xmltodict` (pre-installed V0.12.0 path:`./libs/xmltodict`)
- `pyperclip` (pre-installed V1.8.2 path: `./libs/pyperclip`)
- `pillow`
- `defusedxml`
- `objprint`
- `psutil`
## thanks to
- Open Source Projects
- [pyglet](https://github.com/pyglet/pyglet): GUI and graphics
- `tomlkit` / `rtoml` toml parser
- `xmltodict`: translate data between xml and dict
- `pyperclip`: paste board!
- `rapier2d`: Phy simulate engine
- Main contributors
- [@Rayawa](https://github.com/Rayawa) : check mistake in docs & some translates
- [@rouxiao-you](https://github.com/ruoxiao-you) : translate chinese to English
- [@Billchyi](https://github.com/Billchyi) : check mistake in docs
## Other links
## About License
#### https://creativecommons.org/licenses/by-nc-sa/4.0/
#### Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
This is a human-readable summary of (and not a substitute for) the license. Disclaimer.
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You
may do so in any reasonable manner, but not in any way
that suggests the licensor endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same
license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from
doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is
permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all the permissions necessary for your intended use. For example,
other rights such as publicity, privacy, or moral rights may limit how you use the material.

110
docs/src/README.md Symbolic link
View File

@ -0,0 +1,110 @@
# Difficult Rocket
中文 | [English](/docs/README-en.md)
- [GitHub](https://github.com/shenjackyuanjie/Difficult-Rocket)
- [gitee](https://gitee.com/shenjackyuanjie/Difficult-Rocket)
- [discord](https://discord.gg/kWzw2JrG6M)
- [kook](https://kook.top/sRPjFG)
<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg" alt="996.icu" /></a>
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
[![Generic badge](https://img.shields.io/badge/编写于_Python_版本-3.8.10-blue.svg)](https://Python.org)
[![Generic badge](https://img.shields.io/badge/编写于_Pyglet_版本-2.0.4-blue.svg)](https://pyglet.org)
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_|_3.11_-blue.svg)](https://Python.org)
## 版本
[![Generic badge](https://img.shields.io/badge/Release-0.7.1.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Pre_Release-0.7.1.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![Generic badge](https://img.shields.io/badge/Devloping-0.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
## English README please look [here](./docs/README-en.md)
> 这是一个用Python制作的类Simple Rocket游戏(简称:火箭模拟器)
## 优势
> 相对于原版SR比较“轻量化”
## [计划特性列表](/docs/plan_features)
- [microsoft TODO](https://to-do.microsoft.com/sharing?InvitationToken=Q6SN1kdtitK8cwFktFl71gSnsRMNmrH7CC7kHY_Tq6ReMRwHgInP4_q5ie2IwrHx8)
## [更新日志](/docs/update_logs.md)
## 环境需求 (测试过的 / 开发平台)
- `开发平台 1 - Windows 10 x64 22H2`
- `Python 3.8.10`
- `pillow 9.3.0`
- `pyperclip 1.8.2`
- `pyglet 2.0`
- `psutil 5.9.4`
- `objprint 0.2.2`
- `rtoml 0.9.0`
- `xmltodict 0.13.0`
- `tomlkit 0.11.6`
- `AMD R5 5600X`
- `AMD RX 550 4G`
## 需要的Python模块
- `rtoml`
- `tomlkit`
- `pyglet` (已经内置 V2.0.4 路径:`./libs/pyglet`)
- `xmltodict` (已经内置 V0.12.0 路径:`./libs/xmltodict`)
- `pyperclip` (已经内置 V1.8.2 路径: `./libs/pyperclip`)
- `pillow`
- `defusedxml`
- `objprint`
- `psutil`
## 感谢
- 开源项目
- [pyglet](https://github.com/pyglet/pyglet) : GUI 和画面渲染
- `tomlkit` / `rtoml` : toml 解析器
- `xmltodict`: xml 与 dict 转换器
- `pyperclip`: 剪贴板!
- `rapier2d`: 物理模拟引擎
- 主要贡献者
- [@Rayawa](https://github.com/Rayawa) : 文档矫正 & 翻译部分 lang
- [@rouxiao-you](https://github.com/ruoxiao-you) : 翻译 lang
- [@Billchyi](https://github.com/Billchyi) : 文档矫正
- [@MSDNicrosoft]()
## 相关链接
## 关于分享协议
#### https://creativecommons.org/licenses/by-nc-sa/4.0/
#### 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
这是一份普通人可以理解的许可协议概要 (但不是替代) 。 免责声明.
您可以自由地:
共享 — 在任何媒介以任何形式复制、发行本作品
演绎 — 修改、转换或以本作品为基础进行创作
只要你遵守许可协议条款,许可人就无法收回你的这些权利。
惟须遵守下列条件:
署名 — 您必须给出地当的署名,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。
非商业性使用 — 您不得将本作品用于商业目的。
相同方式共享 — 如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议地同的许可协议 分发您贡献的作品。
没有附加限制 — 您不得适用法律术语或者 技术措施 从而限制其他人做许可协议允许的事情。
声明:
您不必因为公共领域的作品要素而遵守许可协议,或者您的使用被可适用的 例外或限制所允许。
不提供担保。许可协议可能不会给与您意图使用的所必须的所有许可。例如,其他权利比如形象权、隐私权或人格权可能限制您如何使用作品。

View File

@ -1,5 +1,8 @@
# Summary
- [README](./README.md)
- [README-en](./README-en.md)
- [update logs](./update_logs.md)
- [contributors](./contributors.md)

View File

@ -1,3 +1,8 @@
# 如何帮助 DR 开发
aaaa
## 需要准备的工具
1. Powershell 7+
2. Python3.8 +
3. gcc / clang 编译器
4. git

View File

@ -415,6 +415,7 @@ 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
@ -644,8 +645,8 @@ def parse_type_encoding(encoding):
def cfunctype_for_encoding(encoding):
# Check if we've already created a CFUNCTYPE for this encoding.
# If so, then return the cached CFUNCTYPE.
#if encoding in cfunctype_table:
# return cfunctype_table[encoding]
if encoding in cfunctype_table:
return cfunctype_table[encoding]
# Otherwise, create a new CFUNCTYPE for the encoding.
typecodes = {b'c': c_char, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong,
@ -668,7 +669,7 @@ def cfunctype_for_encoding(encoding):
# Cache the new CFUNCTYPE in the cfunctype_table.
# We do this mainly because it prevents the CFUNCTYPE
# from being garbage-collected while we need it.
#cfunctype_table[encoding] = cfunctype
cfunctype_table[encoding] = cfunctype
return cfunctype
@ -728,12 +729,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 = {}

View File

@ -861,7 +861,7 @@ class Mat4(tuple):
def look_at(cls: type[Mat4T], position: Vec3, target: Vec3, up: Vec3):
f = (target - position).normalize()
u = up.normalize()
s = f.cross(u)
s = f.cross(u).normalize()
u = s.cross(f)
return cls([s.x, u.x, -f.x, 0.0,

View File

@ -135,17 +135,6 @@ 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.
@ -409,7 +398,7 @@ class StaticSource(Source):
audio_data = source.get_audio_data(buffer_size)
if not audio_data:
break
data.write(audio_data.get_string_data())
data.write(audio_data.data)
self._data = data.getvalue()
self._duration = len(self._data) / self.audio_format.bytes_per_second

View File

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

View File

@ -1,10 +1,11 @@
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, MediaEvent
from pyglet.media.drivers.base import AbstractAudioDriver, AbstractAudioPlayer
from pyglet.media.drivers.listener import AbstractListener
_debug = debug_print('debug_media')
@ -75,7 +76,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
self._play_cursor_ring = 0
self._write_cursor_ring = 0
# List of (play_cursor, MediaEvent), in sort order
# List of play_cursor, in sort order
self._events = []
# List of (cursor, timestamp), in sort order (cursor gives expiry
@ -157,9 +158,6 @@ 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()
@ -246,7 +244,7 @@ class DirectSoundAudioPlayer(AbstractAudioPlayer):
if self._playing and self._has_underrun():
assert _debug('underrun, stopping')
self.stop()
self._dispatch_new_event('on_eos')
pyglet.app.platform_event_loop.post_event(self.player, 'on_eos')
def _get_write_size(self):
self.update_play_cursor()

View File

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

View File

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

View File

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

View File

@ -623,6 +623,80 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
"""
raise NotImplementedError('abstract')
# Public methods (sort alphabetically):
def activate(self):
"""Attempt to restore keyboard focus to the window.
Depending on the window manager or operating system, this may not
be successful. For example, on Windows XP an application is not
allowed to "steal" focus from another application. Instead, the
window's taskbar icon will flash, indicating it requires attention.
"""
raise NotImplementedError('abstract')
@staticmethod
def clear():
"""Clear the window.
This is a convenience method for clearing the color and depth
buffer. The window must be the active context (see `switch_to`).
"""
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
def close(self):
"""Close the window.
After closing the window, the GL context will be invalid. The
window instance cannot be reused once closed (see also `set_visible`).
The `pyglet.app.EventLoop.on_window_close` event is dispatched on
`pyglet.app.event_loop` when this method is called.
"""
from pyglet import app
if not self._context:
return
app.windows.remove(self)
self._context.destroy()
self._config = None
self._context = None
if app.event_loop:
app.event_loop.dispatch_event('on_window_close', self)
self._event_queue = []
def dispatch_event(self, *args):
if not self._enable_event_queue or self._allow_dispatch_event:
super().dispatch_event(*args)
else:
self._event_queue.append(args)
def dispatch_events(self):
"""Poll the operating system event queue for new events and call
attached event handlers.
This method is provided for legacy applications targeting pyglet 1.0,
and advanced applications that must integrate their event loop
into another framework.
Typical applications should use `pyglet.app.run`.
"""
raise NotImplementedError('abstract')
def draw_mouse_cursor(self):
"""Draw the custom mouse cursor.
If the current mouse cursor has ``drawable`` set, this method
is called before the buffers are flipped to render it.
There is little need to override this method; instead, subclass
:py:class:`MouseCursor` and provide your own
:py:meth:`~MouseCursor.draw` method.
"""
# Draw mouse cursor if set and visible.
if self._mouse_cursor.gl_drawable and self._mouse_visible and self._mouse_in_window:
# TODO: consider projection differences
self._mouse_cursor.draw(self._mouse_x, self._mouse_y)
def flip(self):
"""Swap the OpenGL front and back buffers.
@ -635,13 +709,124 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
"""
raise NotImplementedError('abstract')
def switch_to(self):
"""Make this window the current OpenGL rendering context.
def get_framebuffer_size(self):
"""Return the size in actual pixels of the Window framebuffer.
When using HiDPI screens, the size of the Window's framebuffer
can be higher than that of the Window size requested. If you
are performing operations that require knowing the actual number
of pixels in the window, this method should be used instead of
:py:func:`Window.get_size()`. For example, setting the Window
projection or setting the glViewport size.
:rtype: (int, int)
:return: The width and height of the Window's framebuffer, in pixels.
"""
return self.get_size()
def get_location(self):
"""Return the current position of the window.
:rtype: (int, int)
:return: The distances of the left and top edges from their respective
edges on the virtual desktop, in pixels.
"""
raise NotImplementedError('abstract')
def get_pixel_ratio(self):
"""Return the framebuffer/window size ratio.
Some platforms and/or window systems support subpixel scaling,
making the framebuffer size larger than the window size.
Retina screens on OS X and Gnome on Linux are some examples.
On a Retina systems the returned ratio would usually be 2.0 as a
window of size 500 x 500 would have a framebuffer of 1000 x 1000.
Fractional values between 1.0 and 2.0, as well as values above
2.0 may also be encountered.
:rtype: float
:return: The framebuffer/window size ratio
"""
return self.get_framebuffer_size()[0] / self.width
def get_size(self) -> Tuple[int, int]:
"""Return the current size of the window.
This does not include the windows' border or title bar.
:rtype: (int, int)
:return: The width and height of the window, in pixels.
"""
return self._width, self._height
def get_system_mouse_cursor(self, name):
"""Obtain a system mouse cursor.
Use `set_mouse_cursor` to make the cursor returned by this method
active. The names accepted by this method are the ``CURSOR_*``
constants defined on this class.
:Parameters:
`name` : str
Name describing the mouse cursor to return. For example,
``CURSOR_WAIT``, ``CURSOR_HELP``, etc.
:rtype: `MouseCursor`
:return: A mouse cursor which can be used with `set_mouse_cursor`.
"""
raise NotImplementedError()
def minimize(self):
"""Minimize the window.
"""
raise NotImplementedError('abstract')
def maximize(self):
"""Maximize the window.
The behaviour of this method is somewhat dependent on the user's
display setup. On a multi-monitor system, the window may maximize
to either a single screen or the entire virtual desktop.
"""
raise NotImplementedError('abstract')
def on_close(self):
"""Default on_close handler."""
self.has_exit = True
from pyglet import app
if app.event_loop.is_running:
self.close()
def on_key_press(self, symbol, modifiers):
"""Default on_key_press handler."""
if symbol == key.ESCAPE and not (modifiers & ~(key.MOD_NUMLOCK |
key.MOD_CAPSLOCK |
key.MOD_SCROLLLOCK)):
self.dispatch_event('on_close')
def on_resize(self, width, height):
"""A default resize event handler.
This default handler updates the GL viewport to cover the entire
window. The bottom-left corner is (0, 0) and the top-right
corner is the width and height of the window's framebuffer.
In addition, the projection matrix is set to an orghogonal
projection based on the same dimensions.
"""
gl.glViewport(0, 0, *self.get_framebuffer_size())
self.projection = Mat4.orthogonal_projection(0, width, 0, height, -255, 255)
def set_caption(self, caption):
"""Set the window's caption.
The caption appears in the titlebar of the window, if it has one,
and in the taskbar on Windows and many X11 window managers.
:Parameters:
`caption` : str or unicode
The caption to set.
Only one OpenGL context can be active at a time. This method sets
the current window's context to be current. You should use this
method in preference to `pyglet.gl.Context.set_current`, as it may
perform additional initialisation functions.
"""
raise NotImplementedError('abstract')
@ -740,69 +925,233 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
height = self.screen.height
return width, height
def on_resize(self, width, height):
"""A default resize 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 updates the GL viewport to cover the entire
window. The bottom-left corner is (0, 0) and the top-right
corner is the width and height of the window's framebuffer.
In addition, the projection matrix is set to an orghogonal
projection based on the same dimensions.
"""
gl.glViewport(0, 0, *self.get_framebuffer_size())
self.projection = Mat4.orthogonal_projection(0, width, 0, height, -255, 255)
if width < 1 or height < 1:
raise ValueError('width and height must be positive integers')
def on_close(self):
"""Default on_close handler."""
self.has_exit = True
from pyglet import app
if app.event_loop.is_running:
self.close()
self._minimum_size = width, height
def on_key_press(self, symbol, modifiers):
"""Default on_key_press handler."""
if symbol == key.ESCAPE and not (modifiers & ~(key.MOD_NUMLOCK |
key.MOD_CAPSLOCK |
key.MOD_SCROLLLOCK)):
self.dispatch_event('on_close')
def set_maximum_size(self, width: int, height: int) -> None:
"""Set the maximum size of the window.
def close(self):
"""Close the window.
Once set, the user will not be able to resize the window larger
than the given dimensions. There is no way to remove the
maximum size constraint on a window (but you could set it to a large
value).
After closing the window, the GL context will be invalid. The
window instance cannot be reused once closed (see also `set_visible`).
The behaviour is undefined if the maximum size is set smaller than
the current size of the window.
The window size does not include the border or title bar.
:Parameters:
`width` : int
Maximum width of the window, in pixels.
`height` : int
Maximum height of the window, in pixels.
The `pyglet.app.EventLoop.on_window_close` event is dispatched on
`pyglet.app.event_loop` when this method is called.
"""
from pyglet import app
if not self._context:
return
app.windows.remove(self)
self._context.destroy()
self._config = None
self._context = None
if app.event_loop:
app.event_loop.dispatch_event('on_window_close', self)
self._event_queue = []
if width < 1 or height < 1:
raise ValueError('width and height must be positive integers')
def draw_mouse_cursor(self):
"""Draw the custom mouse cursor.
self._maximum_size = width, height
If the current mouse cursor has ``drawable`` set, this method
is called before the buffers are flipped to render it.
def set_size(self, width: int, height: int) -> None:
"""Resize the window.
The behaviour is undefined if the window is not resizable, or if
it is currently fullscreen.
The window size does not include the border or title bar.
:Parameters:
`width` : int
New width of the window, in pixels.
`height` : int
New height of the window, in pixels.
There is little need to override this method; instead, subclass
:py:class:`MouseCursor` and provide your own
:py:meth:`~MouseCursor.draw` method.
"""
# Draw mouse cursor if set and visible.
if self._fullscreen:
raise WindowException('Cannot set size of fullscreen window.')
if width < 1 or height < 1:
raise ValueError('width and height must be positive integers')
if self._mouse_cursor.gl_drawable and self._mouse_visible and self._mouse_in_window:
# TODO: consider projection differences
self._mouse_cursor.draw(self._mouse_x, self._mouse_y)
self._width, self._height = width, height
# These properties provide read-only access to instance variables.
def set_location(self, x, y):
"""Set the position of the window.
:Parameters:
`x` : int
Distance of the left edge of the window from the left edge
of the virtual desktop, in pixels.
`y` : int
Distance of the top edge of the window from the top edge of
the virtual desktop, in pixels.
"""
raise NotImplementedError('abstract')
def set_visible(self, visible: bool = True) -> None:
"""Show or hide the window.
:Parameters:
`visible` : bool
If True, the window will be shown; otherwise it will be
hidden.
"""
self._visible = visible
def set_vsync(self, vsync: bool) -> None:
"""Enable or disable vertical sync control.
When enabled, this option ensures flips from the back to the front
buffer are performed only during the vertical retrace period of the
primary display. This can prevent "tearing" or flickering when
the buffer is updated in the middle of a video scan.
Note that LCD monitors have an analogous time in which they are not
reading from the video buffer; while it does not correspond to
a vertical retrace it has the same effect.
Also note that with multi-monitor systems the secondary monitor
cannot be synchronised to, so tearing and flicker cannot be avoided
when the window is positioned outside of the primary display.
:Parameters:
`vsync` : bool
If True, vsync is enabled, otherwise it is disabled.
"""
self._vsync = vsync
def set_mouse_visible(self, visible=True):
"""Show or hide the mouse cursor.
The mouse cursor will only be hidden while it is positioned within
this window. Mouse events will still be processed as usual.
:Parameters:
`visible` : bool
If True, the mouse cursor will be visible, otherwise it
will be hidden.
"""
self._mouse_visible = visible
self.set_mouse_platform_visible()
def set_mouse_platform_visible(self, platform_visible=None):
"""Set the platform-drawn mouse cursor visibility. This is called
automatically after changing the mouse cursor or exclusive mode.
Applications should not normally need to call this method, see
`set_mouse_visible` instead.
:Parameters:
`platform_visible` : bool or None
If None, sets platform visibility to the required visibility
for the current exclusive mode and cursor type. Otherwise,
a bool value will override and force a visibility.
"""
raise NotImplementedError()
def set_mouse_cursor(self, cursor=None):
"""Change the appearance of the mouse cursor.
The appearance of the mouse cursor is only changed while it is
within this window.
:Parameters:
`cursor` : `MouseCursor`
The cursor to set, or None to restore the default cursor.
"""
if cursor is None:
cursor = DefaultMouseCursor()
self._mouse_cursor = cursor
self.set_mouse_platform_visible()
def set_exclusive_mouse(self, exclusive=True):
"""Hide the mouse cursor and direct all mouse events to this
window.
When enabled, this feature prevents the mouse leaving the window. It
is useful for certain styles of games that require complete control of
the mouse. The position of the mouse as reported in subsequent events
is meaningless when exclusive mouse is enabled; you should only use
the relative motion parameters ``dx`` and ``dy``.
:Parameters:
`exclusive` : bool
If True, exclusive mouse is enabled, otherwise it is disabled.
"""
self._mouse_exclusive = exclusive
def set_exclusive_keyboard(self, exclusive=True):
"""Prevent the user from switching away from this window using
keyboard accelerators.
When enabled, this feature disables certain operating-system specific
key combinations such as Alt+Tab (Command+Tab on OS X). This can be
useful in certain kiosk applications, it should be avoided in general
applications or games.
:Parameters:
`exclusive` : bool
If True, exclusive keyboard is enabled, otherwise it is
disabled.
"""
self._keyboard_exclusive = exclusive
def set_icon(self, *images):
"""Set the window icon.
If multiple images are provided, one with an appropriate size
will be selected (if the correct size is not provided, the image
will be scaled).
Useful sizes to provide are 16x16, 32x32, 64x64 (Mac only) and
128x128 (Mac only).
:Parameters:
`images` : sequence of `pyglet.image.AbstractImage`
List of images to use for the window icon.
"""
pass
def switch_to(self):
"""Make this window the current OpenGL rendering context.
Only one OpenGL context can be active at a time. This method sets
the current window's context to be current. You should use this
method in preference to `pyglet.gl.Context.set_current`, as it may
perform additional initialisation functions.
"""
raise NotImplementedError('abstract')
# Attributes (sort alphabetically):
@property
def caption(self):
"""The window caption (title). Read-only.
@ -935,6 +1284,9 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
def projection(self):
"""The OpenGL window projection matrix. Read-write.
This matrix is used to transform vertices when using any of the built-in
drawable classes. `view` is done first, then `projection`.
The default projection matrix is orthographic (2D),
but a custom :py:class:`pyglet.math.Mat4` instance
can be set. Alternatively, you can supply a flat
@ -959,6 +1311,9 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
def view(self):
"""The OpenGL window view matrix. Read-write.
This matrix is used to transform vertices when using any of the built-in
drawable classes. `view` is done first, then `projection`.
The default view is an identity matrix, but a custom
:py:class:`pyglet.math.Mat4` instance can be set.
Alternatively, you can supply a flat tuple of 16 values.
@ -1344,6 +1699,118 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
# If documenting, show the event methods. Otherwise, leave them out
# as they are not really methods.
if _is_pyglet_doc_run:
def on_activate(self):
"""The window was activated.
This event can be triggered by clicking on the title bar, bringing
it to the foreground; or by some platform-specific method.
When a window is "active" it has the keyboard focus.
:event:
"""
def on_close(self):
"""The user attempted to close the window.
This event can be triggered by clicking on the "X" control box in
the window title bar, or by some other platform-dependent manner.
The default handler sets `has_exit` to ``True``. In pyglet 1.1, if
`pyglet.app.event_loop` is being used, `close` is also called,
closing the window immediately.
:event:
"""
def on_context_lost(self):
"""The window's GL context was lost.
When the context is lost no more GL methods can be called until it
is recreated. This is a rare event, triggered perhaps by the user
switching to an incompatible video mode. When it occurs, an
application will need to reload all objects (display lists, texture
objects, shaders) as well as restore the GL state.
:event:
"""
def on_context_state_lost(self):
"""The state of the window's GL context was lost.
pyglet may sometimes need to recreate the window's GL context if
the window is moved to another video device, or between fullscreen
or windowed mode. In this case it will try to share the objects
(display lists, texture objects, shaders) between the old and new
contexts. If this is possible, only the current state of the GL
context is lost, and the application should simply restore state.
:event:
"""
def on_deactivate(self):
"""The window was deactivated.
This event can be triggered by clicking on another application
window. When a window is deactivated it no longer has the
keyboard focus.
:event:
"""
def on_draw(self, dt):
"""The window contents must be redrawn.
The `EventLoop` will dispatch this event when the window
should be redrawn. This will happen during idle time after
any window events and after any scheduled functions were called.
The window will already have the GL context, so there is no
need to call `switch_to`. The window's `flip` method will
be called after this event, so your event handler should not.
You should make no assumptions about the window contents when
this event is triggered; a resize or expose event may have
invalidated the framebuffer since the last time it was drawn.
.. versionadded:: 1.1
:event:
"""
def on_expose(self):
"""A portion of the window needs to be redrawn.
This event is triggered when the window first appears, and any time
the contents of the window is invalidated due to another window
obscuring it.
There is no way to determine which portion of the window needs
redrawing. Note that the use of this method is becoming
increasingly uncommon, as newer window managers composite windows
automatically and keep a backing store of the window contents.
:event:
"""
def on_file_drop(self, x, y, paths):
"""File(s) were dropped into the window, will return the position of the cursor and
a list of paths to the files that were dropped.
.. versionadded:: 1.5.1
:event:
"""
def on_hide(self):
"""The window was hidden.
This event is triggered when a window is minimised
or hidden by the user.
:event:
"""
def on_key_press(self, symbol, modifiers):
"""A key on the keyboard was pressed (and held down).
@ -1371,96 +1838,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_text(self, text):
"""The user input some text.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is held down (key repeating); or called without key presses if
another input method was used (e.g., a pen input).
You should always use this method for interpreting text, as the
key symbols often have complex mappings to their unicode
representation which this event takes care of.
:Parameters:
`text` : unicode
The text entered by the user.
:event:
"""
def on_text_motion(self, motion):
"""The user moved the text input cursor.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is help down (key repeating).
You should always use this method for moving the text input cursor
(caret), as different platforms have different default keyboard
mappings, and key repeats are handled correctly.
The values that `motion` can take are defined in
:py:mod:`pyglet.window.key`:
* MOTION_UP
* MOTION_RIGHT
* MOTION_DOWN
* MOTION_LEFT
* MOTION_NEXT_WORD
* MOTION_PREVIOUS_WORD
* MOTION_BEGINNING_OF_LINE
* MOTION_END_OF_LINE
* MOTION_NEXT_PAGE
* MOTION_PREVIOUS_PAGE
* MOTION_BEGINNING_OF_FILE
* MOTION_END_OF_FILE
* MOTION_BACKSPACE
* MOTION_DELETE
:Parameters:
`motion` : int
The direction of motion; see remarks.
:event:
"""
def on_text_motion_select(self, motion):
"""The user moved the text input cursor while extending the
selection.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is help down (key repeating).
You should always use this method for responding to text selection
events rather than the raw :py:meth:`~pyglet.window.Window.on_key_press`, as different platforms
have different default keyboard mappings, and key repeats are
handled correctly.
The values that `motion` can take are defined in :py:mod:`pyglet.window.key`:
* MOTION_UP
* MOTION_RIGHT
* MOTION_DOWN
* MOTION_LEFT
* MOTION_NEXT_WORD
* MOTION_PREVIOUS_WORD
* MOTION_BEGINNING_OF_LINE
* MOTION_END_OF_LINE
* MOTION_NEXT_PAGE
* MOTION_PREVIOUS_PAGE
* MOTION_BEGINNING_OF_FILE
* MOTION_END_OF_FILE
:Parameters:
`motion` : int
The direction of selection motion; see remarks.
:event:
"""
def on_mouse_motion(self, x, y, dx, dy):
"""The mouse was moved with no buttons held down.
@ -1556,19 +1933,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_close(self):
"""The user attempted to close the window.
This event can be triggered by clicking on the "X" control box in
the window title bar, or by some other platform-dependent manner.
The default handler sets `has_exit` to ``True``. In pyglet 1.1, if
`pyglet.app.event_loop` is being used, `close` is also called,
closing the window immediately.
:event:
"""
def on_mouse_enter(self, x, y):
"""The mouse was moved into the window.
@ -1600,36 +1964,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_expose(self):
"""A portion of the window needs to be redrawn.
This event is triggered when the window first appears, and any time
the contents of the window is invalidated due to another window
obscuring it.
There is no way to determine which portion of the window needs
redrawing. Note that the use of this method is becoming
increasingly uncommon, as newer window managers composite windows
automatically and keep a backing store of the window contents.
:event:
"""
def on_resize(self, width, height):
"""The window was resized.
The window will have the GL context when this event is dispatched;
there is no need to call `switch_to` in this handler.
:Parameters:
`width` : int
The new width of the window, in pixels.
`height` : int
The new height of the window, in pixels.
:event:
"""
def on_move(self, x, y):
"""The window was moved.
@ -1645,99 +1979,6 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_activate(self):
"""The window was activated.
This event can be triggered by clicking on the title bar, bringing
it to the foreground; or by some platform-specific method.
When a window is "active" it has the keyboard focus.
:event:
"""
def on_deactivate(self):
"""The window was deactivated.
This event can be triggered by clicking on another application
window. When a window is deactivated it no longer has the
keyboard focus.
:event:
"""
def on_show(self):
"""The window was shown.
This event is triggered when a window is restored after being
minimised, hidden, or after being displayed for the first time.
:event:
"""
def on_hide(self):
"""The window was hidden.
This event is triggered when a window is minimised
or hidden by the user.
:event:
"""
def on_context_lost(self):
"""The window's GL context was lost.
When the context is lost no more GL methods can be called until it
is recreated. This is a rare event, triggered perhaps by the user
switching to an incompatible video mode. When it occurs, an
application will need to reload all objects (display lists, texture
objects, shaders) as well as restore the GL state.
:event:
"""
def on_context_state_lost(self):
"""The state of the window's GL context was lost.
pyglet may sometimes need to recreate the window's GL context if
the window is moved to another video device, or between fullscreen
or windowed mode. In this case it will try to share the objects
(display lists, texture objects, shaders) between the old and new
contexts. If this is possible, only the current state of the GL
context is lost, and the application should simply restore state.
:event:
"""
def on_file_drop(self, x, y, paths):
"""File(s) were dropped into the window, will return the position of the cursor and
a list of paths to the files that were dropped.
.. versionadded:: 1.5.1
:event:
"""
def on_draw(self, dt):
"""The window contents must be redrawn.
The `EventLoop` will dispatch this event when the window
should be redrawn. This will happen during idle time after
any window events and after any scheduled functions were called.
The window will already have the GL context, so there is no
need to call `switch_to`. The window's `flip` method will
be called after this event, so your event handler should not.
You should make no assumptions about the window contents when
this event is triggered; a resize or expose event may have
invalidated the framebuffer since the last time it was drawn.
.. versionadded:: 1.1
:event:
"""
def on_refresh(self, dt):
"""The window contents must be redrawn.
@ -1757,6 +1998,120 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
:event:
"""
def on_resize(self, width, height):
"""The window was resized.
The window will have the GL context when this event is dispatched;
there is no need to call `switch_to` in this handler.
: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.
This event is triggered when a window is restored after being
minimised, hidden, or after being displayed for the first time.
:event:
"""
def on_text(self, text):
"""The user input some text.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is held down (key repeating); or called without key presses if
another input method was used (e.g., a pen input).
You should always use this method for interpreting text, as the
key symbols often have complex mappings to their unicode
representation which this event takes care of.
:Parameters:
`text` : unicode
The text entered by the user.
:event:
"""
def on_text_motion(self, motion):
"""The user moved the text input cursor.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is help down (key repeating).
You should always use this method for moving the text input cursor
(caret), as different platforms have different default keyboard
mappings, and key repeats are handled correctly.
The values that `motion` can take are defined in
:py:mod:`pyglet.window.key`:
* MOTION_UP
* MOTION_RIGHT
* MOTION_DOWN
* MOTION_LEFT
* MOTION_NEXT_WORD
* MOTION_PREVIOUS_WORD
* MOTION_BEGINNING_OF_LINE
* MOTION_END_OF_LINE
* MOTION_NEXT_PAGE
* MOTION_PREVIOUS_PAGE
* MOTION_BEGINNING_OF_FILE
* MOTION_END_OF_FILE
* MOTION_BACKSPACE
* MOTION_DELETE
:Parameters:
`motion` : int
The direction of motion; see remarks.
:event:
"""
def on_text_motion_select(self, motion):
"""The user moved the text input cursor while extending the
selection.
Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
:py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
is help down (key repeating).
You should always use this method for responding to text selection
events rather than the raw :py:meth:`~pyglet.window.Window.on_key_press`, as different platforms
have different default keyboard mappings, and key repeats are
handled correctly.
The values that `motion` can take are defined in :py:mod:`pyglet.window.key`:
* MOTION_UP
* MOTION_RIGHT
* MOTION_DOWN
* MOTION_LEFT
* MOTION_NEXT_WORD
* MOTION_PREVIOUS_WORD
* MOTION_BEGINNING_OF_LINE
* MOTION_END_OF_LINE
* MOTION_NEXT_PAGE
* MOTION_PREVIOUS_PAGE
* MOTION_BEGINNING_OF_FILE
* MOTION_END_OF_FILE
:Parameters:
`motion` : int
The direction of selection motion; see remarks.
:event:
"""
BaseWindow.register_event_type('on_key_press')
BaseWindow.register_event_type('on_key_release')