pyglet update

This commit is contained in:
shenjack 2022-07-16 20:20:23 +08:00
parent 392722cd37
commit 5667dacda6
31 changed files with 814 additions and 347 deletions

View File

@ -79,14 +79,14 @@ def pyglet_load_fonts_folder(folder) -> None:
class ClientWindow(Window):
def __init__(self, net_mode='local', *args, **kwargs):
"""
@param net_mode:
@param args:
@param kwargs:
"""
start_time = time.time_ns()
super().__init__(*args, **kwargs)
"""
:param dev_list: 共享内存
:param dev_dic: 共享内存
:param logger: logger
:param net_mode: 网络模式 # local / ip
"""
# logging
self.logger = logging.getLogger('client')
# value

View File

@ -7,19 +7,43 @@
#include <Python.h>
#include <stdint.h>
int *print_PyUcs4(PyObject *pyObject){
if(PyUnicode_READY(pyObject) == -1){
PyErr_SetString(PyExc_UnicodeDecodeError, "failed");
return NULL;
}
#if defined(__linux__)
const char *out_char = PyUnicode_AsUTF8(pyObject);
printf("%s\n", out_char);
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
Py_UCS4 *ucs4 = PyUnicode_4BYTE_DATA(pyObject);
printf("%ws\n", ucs4); // win
#endif
return (int *) 1;
};
static PyObject *pycprint_print(PyObject *self, PyObject *args){
const char *text;
if(!PyArg_ParseTuple(args, "s", &text)){ // 解析 text
if(!print_PyUcs4(PyTuple_GetItem(args, 0))){
return NULL;
};
printf("%s", text);
Py_RETURN_NONE;
};
Py_UCS4 *get_ucs4from_unicode(PyObject *unicode){
const char *get_char_from_PyUnicode(PyObject *unicodeObject){
if(!PyUnicode_READY(unicodeObject)){
return NULL;
};
const char *char_obj = PyUnicode_AsUTF8(unicodeObject);
if(char_obj == NULL){
return NULL;
} else {
return char_obj;
};
};
static PyObject *pycpint_printf(PyObject *self, PyObject *args, PyObject *kwargs){
printf("args == NULL: %d\n", args == NULL);
printf("kwargs == NULL: %d\n", kwargs == NULL);
@ -34,14 +58,13 @@ static PyObject *pycpint_printf(PyObject *self, PyObject *args, PyObject *kwargs
for (Py_ssize_t i = 0; i < text_len; i++){ // for 遍历
cache_obj = PyTuple_GetItem(args, i); // 获取一个字符串
if (cache_obj == NULL){ return NULL; }; // 出毛病了就报错
if (PyUnicode_Check(cache_obj) == 1) { // 他不是个字符串(实际上如果有pyi文件就不需要这个检查)
if (PyUnicode_Check(cache_obj) == 1) {
print_PyUcs4(cache_obj);
} else if (PyList_Check(cache_obj) == 1) {
};
};
printf("text_len = %lld\n", (Py_size)text_len);
};
if(kwargs != NULL){ // 传入了 end 或者 sep
Py_ssize_t kwargs_len = PyDict_Size(kwargs);
@ -49,13 +72,8 @@ static PyObject *pycpint_printf(PyObject *self, PyObject *args, PyObject *kwargs
if(PyDict_Contains(kwargs, PyUnicode_FromString("end"))){ // 如果包含 end 的参数
PyObject *end_unicode; // 整个缓存
end_unicode = PyDict_GetItemString(kwargs, "end"); // 先获取出来 Pyobj
if(!PyUnicode_READY(end_unicode)){ return NULL; }; // 确认这个字符串对象可以用宏
Py_ssize_t end_unicode_len = PyUnicode_GetLength(end_unicode); // 缓存一手长度
Py_UCS4 *new_end = malloc(sizeof(char) * end_unicode_len); // 提前分配好不定长度的字符串内存
for (Py_ssize_t i = 0; i < end_unicode_len; i++){
new_end[i] = PyUnicode_ReadChar(end_unicode, i); // 每一位对应读取
};
end = PyUnicode_AsUTF8(end_unicode);
};
};
printf("%s", end);

View File

@ -3,11 +3,22 @@
# Copyright © 2021-2022 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import timeit
import time
from build import pycprint
pycprint.print("a啊a\n")
time.sleep(1)
cpyprint = timeit.timeit('pycprint.print("啊啊")', number=100000, globals=globals())
pyprint = timeit.timeit('print("啊啊")', number=100000, globals=globals())
print(cpyprint, pyprint, pyprint - cpyprint)
time.sleep(10)
pycprint.print("a啊 a\n")
pycprint.print("a啊采购好难过 a\n")
print()
#
pycprint.printf("a啊a\n", "aaa", "aaa", end='')
print()

View File

@ -29,13 +29,15 @@ class Threads(threading.Thread):
class ThreadLock:
def __init__(self, the_lock: Lock) -> None:
def __init__(self, the_lock: Lock, time_out: Union[float, int] = 1/60) -> None:
self.lock = the_lock
self.time_out = time_out
def __enter__(self, timeout: Union[float, int] = 1/60):
self.lock.acquire(timeout=timeout)
def __enter__(self):
self.lock.acquire(timeout=self.time_out)
if not self.lock.locked():
raise LockTimeOutError
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if (exc_type is None) and (exc_val is None) and (exc_tb is None):

View File

@ -3,14 +3,26 @@
@contact 3695888@qq.com
"""
import atexit
import logging
import threading
from typing import Optional, Union
from time import strftime
from typing import Optional, Union, List, Iterable
from logging import NOTSET, DEBUG, INFO, WARNING, ERROR, FATAL
from Difficult_Rocket.utils.thread import ThreadLock
# 如果想要直接使用 logger 来 logging
# 直接调用 logger.debug() 即可
# 默认配置会有
# ----------
# 配置方式一
# 直接使用 logger.Logger()
# 将会创建一个空 logger
# 可以自行通过
# 配置方式二
#
#
color_reset_suffix = "\033[0m"
"""
@ -28,7 +40,6 @@ NOTSET = 0
DETAIL = 5
class LogFileCache:
"""日志文件缓存"""
@ -43,23 +54,21 @@ class LogFileCache:
self._logfile_name = file_name # log 文件名称
self.flush_time = flush_time # 缓存刷新时长
self.cache_entries_num = log_cache_lens_max
# 写入缓存数
self.cache_count = 0
# 日志缓存表
self.logs_cache = []
self._log_cache = []
# 同步锁
self.thread_lock = threading.Lock()
self.with_thread_lock = ThreadLock(self.thread_lock)
self.threaded_write = threading.Timer(1, self._log_file_time_write)
self.cache_lock = threading.Lock() # 主锁
self.write_lock = threading.Lock() # 写入锁
self.with_thread_lock = ThreadLock(self.cache_lock, time_out=1 / 60) # 直接用于 with 的主锁
self.threaded_write = threading.Timer(1, self._log_file_time_write) # 基于 timer 的多线程
def end_thread(self):
def end_thread(self) -> None:
"""结束日志写入进程,顺手把目前的缓存写入"""
self.thread_lock.acquire(blocking=True)
self.cache_lock.acquire(blocking=True)
self.threaded_write.cancel()
if self.cache_count:
self._log_file_time_write()
self._log_file_time_write()
def start_thread(self):
def start_thread(self) -> None:
self.threaded_write.start()
atexit.register(self.end_thread)
@ -72,17 +81,34 @@ class LogFileCache:
with self.with_thread_lock:
self._logfile_name = value
@property
def log_caches(self) -> list:
return self._log_cache
@log_caches.setter
def log_caches(self, value: Union[str, Iterable[str]]):
if type(value) == str:
with self.with_thread_lock:
self._log_cache.append(value)
return
elif isinstance(value, Iterable):
with self.with_thread_lock:
list(map(self._log_cache.append, value))
...
def _log_file_time_write(self) -> None:
"""使用 threading.Timer 调用的定时写入日志文件的函数"""
with self.with_thread_lock:
if self.cache_count == 0:
return None
if self.log_caches:
with self.with_thread_lock:
if self.log_caches:
...
...
def write_logs(self, string: str, wait_for_cache: bool = True) -> None:
if wait_for_cache:
with open(file=self.logfile_name, encoding='utf-8', mode='a') as log_file:
log_file.writelines(self.logs_cache)
with self.with_thread_lock and open(file=self.logfile_name, encoding='utf-8', mode='a') as log_file:
log_file.writelines(self._log_cache)
log_file.write(string)
...
else:
@ -92,82 +118,118 @@ class LogFileCache:
class Logger:
"""shenjack logger"""
def __init__(self, config: dict = None, **kwargs) -> None:
"""请注意,如果需要获取一个"""
def __init__(self, **kwargs) -> None:
"""
配置模式: 使用 kwargs 配置
@param config: 字典格式的配置
@param kwargs: key word 格式的配置
"""
self.name = 'root'
if config is None:
if name := kwargs.pop('name', default=False):
self.level = DEBUG
self.colors = None
if kwargs is not None: # 使用 kwargs 尝试配置
if name := kwargs.pop('name', False): # 顺手把获取到的配置填入临时变量 如果成功获取再填入 self
self.name = name
if level := kwargs.pop('level', False):
self.level = level
else:
...
def make_log(self, level: int,
*values: object,
self.file_cache = LogFileCache()
self.warn = self.warning
self.fine = self.detail
def make_log(self, *values: object,
level: int,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
if level < self.level:
return None
print(level, values, sep, end, flush, sep='|')
write_text = sep.join(*values)
print(write_text)
...
def detail(self, *values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=DETAIL, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=DETAIL, sep=sep, end=end, flush=flush)
def debug(self,
*values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=DEBUG, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=DEBUG, sep=sep, end=end, flush=flush)
def info(self,
*values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=INFO, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=INFO, sep=sep, end=end, flush=flush)
def warning(self,
*values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=WARNING, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=WARNING, sep=sep, end=end, flush=flush)
def error(self,
*values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=ERROR, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=ERROR, sep=sep, end=end, flush=flush)
def fatal(self,
*values: object,
sep: Optional[str] = ' ',
end: Optional[str] = '\n',
flush: Optional[bool] = False) -> None:
self.make_log(level=FATAL, *values, sep=sep, end=end, flush=flush)
self.make_log(*values, level=FATAL, sep=sep, end=end, flush=flush)
logger_configs = {
'root': {
'level': DEBUG,
'color': {
DEBUG: '\033[0m'
'Logger': {
'root': {
'level': DETAIL,
'color': {
DEBUG: '\033[0m'
},
'file': 'main_log_file',
},
'file': {
},
'Color': {
'detail'
},
'File': {
'main_log_file': {
'mode': 'a',
'encoding': 'utf-8'
'encoding': 'utf-8',
'level': DEBUG,
'file_name': '{main_time}_logs.md'
},
},
'Formatter': {
'main_time': {'strftime': '%Y-%m-%d %H-%M-%S'},
'version': 'game.version',
'level': 'level',
'encoding': 'utf-8',
...: ...
}
}
def add_dict_config_to_global(some_dict: dict, name: str) -> dict:
def add_dict_config_to_global(some_dict: Union[dict, list, str], name: str) -> dict:
"""
提前声明这个函数很有可能搞坏 config
请使用 add_kwargs_to_global 来修改配置
如果你不知道你在改什么**务必不要**用这个函数来修改配置
@param some_dict: 一个你丢进来的 logger 设置
@param name: 这个 logger 设置的名称
@return: 修改过的 logger 配置
@ -184,3 +246,19 @@ def add_kwargs_to_global(**kwargs) -> dict:
"""
...
def get_logger(name: str = 'name') -> Logger:
"""
此函数用于从 global_config 中取出对应的配置建立一个相应的 logger
@param name: logger的名称
@return: 创建好的 logger
"""
...
if __name__ == "__main__":
# 在这里可以使用 add_kwargs_to_global
some_logger = Logger(name='aaa')
some_logger.level = DETAIL
some_logger.warn('aaaa', 'aaaa')
...

View File

@ -44,7 +44,7 @@ import sys
from typing import TYPE_CHECKING
#: The release version
version = '2.0.dev18'
version = '2.0.dev20'
__version__ = version
if sys.version_info < (3, 6):
@ -149,6 +149,7 @@ options = {
'debug_trace_depth': 1,
'debug_trace_flush': True,
'debug_win32': False,
'debug_input': False,
'debug_x11': False,
'shadow_window': True,
'vsync': None,
@ -178,6 +179,7 @@ _option_types = {
'debug_trace_depth': int,
'debug_trace_flush': bool,
'debug_win32': bool,
'debug_input': bool,
'debug_x11': bool,
'shadow_window': bool,
'vsync': bool,

View File

@ -245,7 +245,7 @@ class EventLoop(event.EventDispatcher):
self.clock.call_scheduled_functions(dt)
# Update timout
return self.clock.get_sleep_time(False)
return self.clock.get_sleep_time(True)
@property
def has_exit(self):

View File

@ -38,19 +38,19 @@
Measuring time
==============
The `tick` and `get_fps` functions can be used in conjunction to fulfil most
The `tick` and `get_frequency` functions can be used in conjunction to fulfil most
games' basic requirements::
from pyglet import clock
while True:
dt = clock.tick()
# ... update and render ...
print(f"FPS is {clock.get_fps()}")
print(f"FPS is {clock.get_frequency()}")
The ``dt`` value returned gives the number of seconds (as a float) since the
last "tick".
The `get_fps` function averages the framerate over a sliding window of
The `get_frequency` function averages the framerate over a sliding window of
approximately 1 second. (You can calculate the instantaneous framerate by
taking the reciprocal of ``dt``).
@ -176,7 +176,7 @@ class Clock:
self.next_ts = self.time()
self.last_ts = None
# Used by self.get_fps to show update frequency
# Used by self.get_frequency to show update frequency
self.times = _deque()
self.cumulative_time = 0
self.window_size = 60

View File

@ -114,22 +114,34 @@ def load(name=None, size=None, bold=False, italic=False, stretch=False, dpi=None
if dpi is None:
dpi = 96
# Find first matching name
if type(name) in (tuple, list):
for n in name:
if _font_class.have_font(n):
name = n
break
else:
name = None
# Locate or create font cache
shared_object_space = gl.current_context.object_space
if not hasattr(shared_object_space, 'pyglet_font_font_cache'):
shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
shared_object_space.pyglet_font_font_hold = []
shared_object_space.pyglet_font_font_name_match = {} # Match a tuple to specific name to reduce lookups.
font_cache = shared_object_space.pyglet_font_font_cache
font_hold = shared_object_space.pyglet_font_font_hold
font_name_match = shared_object_space.pyglet_font_font_name_match
name_type = type(name)
if name_type in (tuple, list):
if name_type == list:
name = tuple(name)
if name in font_name_match:
name = font_name_match[name]
else:
# Find first matching name, cache it.
found_name = None
for n in name:
if _font_class.have_font(n):
found_name = n
break
font_name_match[name] = found_name
name = found_name
# Look for font name in font cache
descriptor = (name, size, bold, italic, stretch, dpi)

View File

@ -44,8 +44,7 @@ __all__ = ['link_GL', 'link_WGL']
_debug_trace = pyglet.options['debug_trace']
# gl_lib = ctypes.windll.opengl32
gl_lib = pyglet.lib.load_library('opengl32')
gl_lib = ctypes.windll.opengl32
wgl_lib = gl_lib
if _debug_trace:

View File

@ -202,7 +202,7 @@ def draw(size, mode, **data):
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize)
assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt
buffer = BufferObject(size * attribute.stride, GL_ARRAY_BUFFER)
buffer = BufferObject(size * attribute.stride)
attribute.set_region(buffer, 0, size, array)
attribute.enable()
attribute.set_pointer(buffer.ptr)
@ -252,7 +252,7 @@ def draw_indexed(size, mode, indices, **data):
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize)
assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt
buffer = BufferObject(size * attribute.stride, GL_ARRAY_BUFFER)
buffer = BufferObject(size * attribute.stride)
attribute.set_region(buffer, 0, size, array)
attribute.enable()
attribute.set_pointer(buffer.ptr)
@ -271,8 +271,9 @@ def draw_indexed(size, mode, indices, **data):
# With GL 3.3 vertex arrays indices needs to be in a buffer
# bound to the ELEMENT_ARRAY slot
index_array = (index_c_type * len(indices))(*indices)
index_buffer = BufferObject(ctypes.sizeof(index_array), GL_ELEMENT_ARRAY_BUFFER)
index_buffer = BufferObject(ctypes.sizeof(index_array))
index_buffer.set_data(index_array)
index_buffer.bind_to_index_buffer()
glDrawElements(mode, len(indices), index_type, 0)
glFlush()
@ -385,7 +386,7 @@ class Batch:
def get_domain(self, indexed, mode, group, program, attributes):
if group is None:
group = get_default_group()
group = ShaderGroup(program=program)
# Batch group
if group not in self.group_map:

View File

@ -484,17 +484,17 @@ class ShaderProgram:
num_active = GLint()
block_data_size = GLint()
glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size)
glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active)
glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size)
indices = (GLuint * num_active.value)()
indices_ptr = cast(addressof(indices), POINTER(GLint))
glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr)
uniforms = {}
for i in range(num_active.value):
uniform_name, u_type, u_size = self._query_uniform(indices[i])
for block_uniform_index in indices:
uniform_name, u_type, u_size = self._query_uniform(block_uniform_index)
# Separate uniform name from block name (Only if instance name is provided on the Uniform Block)
try:
@ -503,9 +503,12 @@ class ShaderProgram:
pass
gl_type, _, _, length, _ = _uniform_setters[u_type]
uniforms[i] = (uniform_name, gl_type, length)
uniforms[block_uniform_index] = (uniform_name, gl_type, length)
uniform_blocks[name] = UniformBlock(self, name, index, block_data_size.value, uniforms)
# This might cause an error if index > GL_MAX_UNIFORM_BUFFER_BINDINGS, but surely no
# one would be crazy enough to use more than 36 uniform blocks, right?
glUniformBlockBinding(self.id, index, index)
if _debug_gl_shaders:
for block in uniform_blocks.values():
@ -638,7 +641,7 @@ class ShaderProgram:
class UniformBlock:
__slots__ = 'program', 'name', 'index', 'size', 'uniforms'
__slots__ = 'program', 'name', 'index', 'size', 'uniforms', 'view_cls'
def __init__(self, program, name, index, size, uniforms):
self.program = proxy(program)
@ -646,23 +649,88 @@ class UniformBlock:
self.index = index
self.size = size
self.uniforms = uniforms
self.view_cls = None
def create_ubo(self, index=0):
return UniformBufferObject(self, index)
"""
Create a new UniformBufferObject from this uniform block.
:Parameters:
`index` : int
The uniform buffer index the returned UBO will bind itself to.
By default this is 0.
:rtype: :py:class:`~pyglet.graphics.shader.UniformBufferObject`
"""
if self.view_cls is None:
self.view_cls = self._introspect_uniforms()
return UniformBufferObject(self.view_cls, self.size, index)
def _introspect_uniforms(self):
"""Introspect the block's structure and return a ctypes struct for
manipulating the uniform block's members.
"""
p_id = self.program.id
index = self.index
active_count = len(self.uniforms)
# Query the uniform index order and each uniform's offset:
indices = (GLuint * active_count)()
offsets = (GLint * active_count)()
indices_ptr = cast(addressof(indices), POINTER(GLint))
offsets_ptr = cast(addressof(offsets), POINTER(GLint))
glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr)
glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_OFFSET, offsets_ptr)
# Offsets may be returned in non-ascending order, sort them with the corresponding index:
_oi = sorted(zip(offsets, indices), key=lambda x: x[0])
offsets = [x[0] for x in _oi] + [self.size]
indices = (GLuint * active_count)(*(x[1] for x in _oi))
# # Query other uniform information:
# gl_types = (GLint * active_count)()
# mat_stride = (GLint * active_count)()
# gl_types_ptr = cast(addressof(gl_types), POINTER(GLint))
# stride_ptr = cast(addressof(mat_stride), POINTER(GLint))
# glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_TYPE, gl_types_ptr)
# glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_MATRIX_STRIDE, stride_ptr)
view_fields = []
for i in range(active_count):
u_name, gl_type, length = self.uniforms[indices[i]]
size = offsets[i+1] - offsets[i]
c_type_size = sizeof(gl_type)
actual_size = c_type_size * length
padding = size - actual_size
# TODO: handle stride for multiple matrixes in the same UBO (crashes now)
# m_stride = mat_stride[i]
arg = (u_name, gl_type * length) if length > 1 else (u_name, gl_type)
view_fields.append(arg)
if padding > 0:
padding_bytes = padding // c_type_size
view_fields.append((f'_padding{i}', gl_type * padding_bytes))
# Custom ctypes Structure for Uniform access:
class View(Structure):
_fields_ = view_fields
__repr__ = lambda self: str(dict(self._fields_))
return View
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name}, index={self.index})"
class UniformBufferObject:
__slots__ = 'block', 'buffer', 'view', '_view', '_view_ptr', 'index'
__slots__ = 'buffer', 'view', '_view_ptr', 'index'
def __init__(self, block, index):
assert type(block) == UniformBlock, "Must be a UniformBlock instance"
self.block = block
self.buffer = BufferObject(self.block.size, GL_UNIFORM_BUFFER)
self.buffer.bind()
self.view = self._introspect_uniforms()
def __init__(self, view_class, buffer_size, index):
self.buffer = BufferObject(buffer_size)
self.view = view_class()
self._view_ptr = pointer(self.view)
self.index = index
@ -670,81 +738,24 @@ class UniformBufferObject:
def id(self):
return self.buffer.id
def _introspect_uniforms(self):
p_id = self.block.program.id
index = self.block.index
# Query the number of active Uniforms:
num_active = GLint()
glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active)
# Query the uniform index order and each uniform's offset:
indices = (GLuint * num_active.value)()
offsets = (GLint * num_active.value)()
indices_ptr = cast(addressof(indices), POINTER(GLint))
offsets_ptr = cast(addressof(offsets), POINTER(GLint))
glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr)
glGetActiveUniformsiv(p_id, num_active.value, indices, GL_UNIFORM_OFFSET, offsets_ptr)
# Offsets may be returned in non-ascending order, sort them with the corresponding index:
_oi = sorted(zip(offsets, indices), key=lambda x: x[0])
offsets = [x[0] for x in _oi] + [self.block.size]
indices = (GLuint * num_active.value)(*(x[1] for x in _oi))
# Query other uniform information:
gl_types = (GLint * num_active.value)()
mat_stride = (GLint * num_active.value)()
gl_types_ptr = cast(addressof(gl_types), POINTER(GLint))
stride_ptr = cast(addressof(mat_stride), POINTER(GLint))
glGetActiveUniformsiv(p_id, num_active.value, indices, GL_UNIFORM_TYPE, gl_types_ptr)
glGetActiveUniformsiv(p_id, num_active.value, indices, GL_UNIFORM_MATRIX_STRIDE, stride_ptr)
args = []
for i in range(num_active.value):
u_name, gl_type, length = self.block.uniforms[indices[i]]
size = offsets[i+1] - offsets[i]
c_type_size = sizeof(gl_type)
actual_size = c_type_size * length
padding = size - actual_size
# TODO: handle stride for multiple matrixes in the same UBO (crashes now)
m_stride = mat_stride[i]
arg = (u_name, gl_type * length) if length > 1 else (u_name, gl_type)
args.append(arg)
if padding > 0:
padding_bytes = padding // c_type_size
args.append((f'_padding{i}', gl_type * padding_bytes))
# Custom ctypes Structure for Uniform access:
class View(Structure):
_fields_ = args
__repr__ = lambda self: str(dict(self._fields_))
return View()
def bind(self, index=None):
glUniformBlockBinding(self.block.program.id, self.block.index, index or self.index)
glBindBufferBase(GL_UNIFORM_BUFFER, index or self.index, self.buffer.id)
glBindBufferBase(GL_UNIFORM_BUFFER, self.index if index is None else index, self.buffer.id)
def read(self):
"""Read the byte contents of the buffer"""
glBindBuffer(GL_UNIFORM_BUFFER, self.buffer.id)
ptr = glMapBufferRange(GL_UNIFORM_BUFFER, 0, self.buffer.size, GL_MAP_READ_BIT)
glBindBuffer(GL_ARRAY_BUFFER, self.buffer.id)
ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, self.buffer.size, GL_MAP_READ_BIT)
data = string_at(ptr, size=self.buffer.size)
glUnmapBuffer(GL_UNIFORM_BUFFER)
glUnmapBuffer(GL_ARRAY_BUFFER)
return data
def __enter__(self):
# Return the view to the user in a `with` context:
glUniformBlockBinding(self.block.program.id, self.block.index, self.index)
glBindBufferBase(GL_UNIFORM_BUFFER, self.index, self.buffer.id)
return self.view
def __exit__(self, exc_type, exc_val, exc_tb):
self.bind()
self.buffer.set_data(self._view_ptr)
def __repr__(self):
return "{0}(id={1})".format(self.block.name + 'Buffer', self.buffer.id)
return "{0}(id={1})".format(self.__class__.__name__, self.buffer.id)

View File

@ -59,8 +59,6 @@ class AbstractBuffer:
`ptr` : int
Memory offset of the buffer, as used by the ``glVertexPointer``
family of functions
`target` : int
OpenGL buffer target, for example ``GL_ARRAY_BUFFER``
`usage` : int
OpenGL buffer usage, for example ``GL_DYNAMIC_DRAW``
@ -69,8 +67,8 @@ class AbstractBuffer:
ptr = 0
size = 0
def bind(self):
"""Bind this buffer to its OpenGL target."""
def bind(self, target=GL_ARRAY_BUFFER):
"""Bind this buffer to an OpenGL target."""
raise NotImplementedError('abstract')
def unbind(self):
@ -170,6 +168,9 @@ class BufferObject(AbstractBuffer):
The data in the buffer is not replicated in any system memory (unless it
is done so by the video driver). While this can improve memory usage and
possibly performance, updates to the buffer are relatively slow.
The target of the buffer is ``GL_ARRAY_BUFFER`` internally to avoid
accidentally overriding other states when altering the buffer contents.
The intended target can be set when binding the buffer.
This class does not implement :py:class:`AbstractMappable`, and so has no
:py:meth:`~AbstractMappable.get_region` method. See
@ -177,9 +178,8 @@ class BufferObject(AbstractBuffer):
that does implement :py:meth:`~AbstractMappable.get_region`.
"""
def __init__(self, size, target, usage=GL_DYNAMIC_DRAW):
def __init__(self, size, usage=GL_DYNAMIC_DRAW):
self.size = size
self.target = target
self.usage = usage
self._context = pyglet.gl.current_context
@ -187,39 +187,43 @@ class BufferObject(AbstractBuffer):
glGenBuffers(1, buffer_id)
self.id = buffer_id.value
glBindBuffer(target, self.id)
glBindBuffer(GL_ARRAY_BUFFER, self.id)
data = (GLubyte * self.size)()
glBufferData(target, self.size, data, self.usage)
glBufferData(GL_ARRAY_BUFFER, self.size, data, self.usage)
def invalidate(self):
glBufferData(self.target, self.size, None, self.usage)
glBufferData(GL_ARRAY_BUFFER, self.size, None, self.usage)
def bind(self):
glBindBuffer(self.target, self.id)
def bind(self, target=GL_ARRAY_BUFFER):
glBindBuffer(target, self.id)
def unbind(self):
glBindBuffer(self.target, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def bind_to_index_buffer(self):
"""Binds this buffer as an index buffer on the active vertex array."""
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.id)
def set_data(self, data):
glBindBuffer(self.target, self.id)
glBufferData(self.target, self.size, data, self.usage)
glBindBuffer(GL_ARRAY_BUFFER, self.id)
glBufferData(GL_ARRAY_BUFFER, self.size, data, self.usage)
def set_data_region(self, data, start, length):
glBindBuffer(self.target, self.id)
glBufferSubData(self.target, start, length, data)
glBindBuffer(GL_ARRAY_BUFFER, self.id)
glBufferSubData(GL_ARRAY_BUFFER, start, length, data)
def map(self):
glBindBuffer(self.target, self.id)
ptr = ctypes.cast(glMapBuffer(self.target, GL_WRITE_ONLY), ctypes.POINTER(ctypes.c_byte * self.size)).contents
glBindBuffer(GL_ARRAY_BUFFER, self.id)
ptr = ctypes.cast(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY), ctypes.POINTER(ctypes.c_byte * self.size)).contents
return ptr
def map_range(self, start, size, ptr_type):
glBindBuffer(self.target, self.id)
ptr = ctypes.cast(glMapBufferRange(self.target, start, size, GL_MAP_WRITE_BIT), ptr_type).contents
glBindBuffer(GL_ARRAY_BUFFER, self.id)
ptr = ctypes.cast(glMapBufferRange(GL_ARRAY_BUFFER, start, size, GL_MAP_WRITE_BIT), ptr_type).contents
return ptr
def unmap(self):
glUnmapBuffer(self.target)
glUnmapBuffer(GL_ARRAY_BUFFER)
def __del__(self):
try:
@ -240,13 +244,13 @@ class BufferObject(AbstractBuffer):
# Map, create a copy, then reinitialize.
temp = (ctypes.c_byte * size)()
glBindBuffer(self.target, self.id)
data = glMapBufferRange(self.target, 0, self.size, GL_MAP_READ_BIT)
glBindBuffer(GL_ARRAY_BUFFER, self.id)
data = glMapBufferRange(GL_ARRAY_BUFFER, 0, self.size, GL_MAP_READ_BIT)
ctypes.memmove(temp, data, min(size, self.size))
glUnmapBuffer(self.target)
glUnmapBuffer(GL_ARRAY_BUFFER)
self.size = size
glBufferData(self.target, self.size, temp, self.usage)
glBufferData(GL_ARRAY_BUFFER, self.size, temp, self.usage)
def __repr__(self):
return f"{self.__class__.__name__}(id={self.id}, size={self.size})"
@ -263,8 +267,8 @@ class MappableBufferObject(BufferObject, AbstractMappable):
Updates to data via :py:meth:`map` are committed immediately.
"""
def __init__(self, size, target, usage=GL_DYNAMIC_DRAW):
super(MappableBufferObject, self).__init__(size, target, usage)
def __init__(self, size, usage=GL_DYNAMIC_DRAW):
super(MappableBufferObject, self).__init__(size, usage)
self.data = (ctypes.c_byte * size)()
self.data_ptr = ctypes.addressof(self.data)
self._dirty_min = sys.maxsize
@ -276,9 +280,9 @@ class MappableBufferObject(BufferObject, AbstractMappable):
size = self._dirty_max - self._dirty_min
if size > 0:
if size == self.size:
glBufferData(self.target, self.size, self.data, self.usage)
glBufferData(GL_ARRAY_BUFFER, self.size, self.data, self.usage)
else:
glBufferSubData(self.target, self._dirty_min, size, self.data_ptr + self._dirty_min)
glBufferSubData(GL_ARRAY_BUFFER, self._dirty_min, size, self.data_ptr + self._dirty_min)
self._dirty_min = sys.maxsize
self._dirty_max = 0
@ -313,8 +317,8 @@ class MappableBufferObject(BufferObject, AbstractMappable):
self.size = size
glBindBuffer(self.target, self.id)
glBufferData(self.target, self.size, self.data, self.usage)
glBindBuffer(GL_ARRAY_BUFFER, self.id)
glBufferData(GL_ARRAY_BUFFER, self.size, self.data, self.usage)
self._dirty_min = sys.maxsize
self._dirty_max = 0

View File

@ -127,7 +127,7 @@ class VertexDomain:
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize)
self.attributes.append(attribute)
# Create buffer:
attribute.buffer = MappableBufferObject(attribute.stride * self.allocator.capacity, GL_ARRAY_BUFFER)
attribute.buffer = MappableBufferObject(attribute.stride * self.allocator.capacity)
attribute.buffer.element_size = attribute.stride
attribute.buffer.attributes = (attribute,)
self.buffer_attributes.append((attribute.buffer, (attribute,)))
@ -370,8 +370,7 @@ class IndexedVertexDomain(VertexDomain):
self.index_gl_type = index_gl_type
self.index_c_type = vertexattribute._c_types[index_gl_type]
self.index_element_size = ctypes.sizeof(self.index_c_type)
self.index_buffer = BufferObject(
self.index_allocator.capacity * self.index_element_size, GL_ELEMENT_ARRAY_BUFFER)
self.index_buffer = BufferObject(self.index_allocator.capacity * self.index_element_size)
def safe_index_alloc(self, count):
"""Allocate indices, resizing the buffers if necessary."""
@ -454,7 +453,7 @@ class IndexedVertexDomain(VertexDomain):
for attribute in attributes:
attribute.enable()
attribute.set_pointer(attribute.buffer.ptr)
self.index_buffer.bind()
self.index_buffer.bind_to_index_buffer()
starts, sizes = self.index_allocator.get_allocated_regions()
primcount = len(starts)
@ -494,7 +493,7 @@ class IndexedVertexDomain(VertexDomain):
for attribute in attributes:
attribute.enable()
attribute.set_pointer(attribute.buffer.ptr)
self.index_buffer.bind()
self.index_buffer.bind_to_index_buffer()
glDrawElements(mode, vertex_list.index_count, self.index_gl_type,
self.index_buffer.ptr +

View File

@ -418,7 +418,7 @@ class TextEntry(WidgetBase):
The color of the outline box in RGBA format.
`text_color` : (int, int, int, int)
The color of the text in RGBA format.
`text_color` : (int, int, int)
`caret_color` : (int, int, int)
The color of the caret in RGB format.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the text entry widget to.

View File

@ -600,7 +600,7 @@ class Controller(EventDispatcher):
`dpleft` : bool
`dpright` : bool
.. versionadded:: 1.2
.. versionadded:: 2.0
"""
self.device = device
@ -1079,7 +1079,7 @@ class TabletCanvas(EventDispatcher):
:event:
"""
def on_motion(self, cursor, x, y, pressure, tilt_x, tilt_y):
def on_motion(self, cursor, x, y, pressure, tilt_x, tilt_y, buttons):
"""The cursor moved on the tablet surface.
If `pressure` is 0, then the cursor is actually hovering above the
@ -1099,6 +1099,9 @@ class TabletCanvas(EventDispatcher):
Currently undefined.
`tilt_y` : float
Currently undefined.
`buttons` : int
Button state may be provided if the platform supports it.
Supported on: Windows
:event:
"""

View File

@ -2,7 +2,7 @@ from pyglet import compat_platform
# This file is automatically generated by 'pyglet/tools/gen_controller_db.py'
# Generated on: Thu Mar 3 16:25:08 2022
# Generated on: Thu Jul 7 09:31:08 2022
if compat_platform.startswith("linux"):
mapping_list = [
@ -216,6 +216,10 @@ if compat_platform.startswith("linux"):
"03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,",
"03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@ -252,8 +256,11 @@ if compat_platform.startswith("linux"):
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
"03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
"03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
"03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
"03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
"03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
@ -266,6 +273,7 @@ if compat_platform.startswith("linux"):
"0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
"030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
"030000004f04000015b3000001010000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
"030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
"030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,",
@ -405,6 +413,7 @@ elif compat_platform.startswith("darwin"):
"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
"03000000222c00000225000000010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
"03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
@ -602,6 +611,7 @@ elif compat_platform.startswith("win"):
"030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000380700006382000000000000,MLG Gamepad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
@ -676,8 +686,9 @@ elif compat_platform.startswith("win"):
"03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,",
"03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,",
"03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,",
"03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000222c00000025000000000000,Qanba Dragon Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",

View File

@ -388,6 +388,7 @@ class EvdevDevice(XlibSelectDevice, Device):
self.control_map[(event_type, event_code)] = control
self.controls.append(control)
self.controls.sort(key=lambda c: c._event_code)
os.close(fileno)
super().__init__(display, name)
@ -553,7 +554,8 @@ class EvdevControllerManager(ControllerManager, XlibSelectDevice):
self._controllers[name] = controller
if controller:
self.dispatch_event('on_connect', controller)
# Dispatch event in main thread:
pyglet.app.platform_event_loop.post_event(self, 'on_connect', controller)
def _make_device(self, name, count=1):
path = os.path.join('/dev/input', name)

View File

@ -34,12 +34,14 @@
# ----------------------------------------------------------------------------
import ctypes
from collections import defaultdict
import pyglet
from pyglet.input.base import DeviceOpenException
from pyglet.input.base import Tablet, TabletCanvas
from pyglet.libs.win32 import libwintab as wintab
from pyglet.util import debug_print
_debug = debug_print('debug_input')
lib = wintab.lib
@ -116,12 +118,19 @@ class WintabTablet(Tablet):
class WintabTabletCanvas(TabletCanvas):
override_keys = False
def __init__(self, device, window, msg_base=wintab.WT_DEFBASE):
super(WintabTabletCanvas, self).__init__(window)
self.device = device
self.msg_base = msg_base
# Get the extension masks available. Only need to do this once.
global _extension_masks
if not _extension_masks:
_extension_masks = get_extension_masks()
# Just use system context, for similarity w/ os x and xinput.
# WTI_DEFCONTEXT detaches mouse from tablet, which is nice, but not
# possible on os x afiak.
@ -132,11 +141,11 @@ class WintabTabletCanvas(TabletCanvas):
# If you change this, change definition of PACKET also.
context_info.lcPktData = (
wintab.PK_CHANGED | wintab.PK_CURSOR | wintab.PK_BUTTONS |
wintab.PK_X | wintab.PK_Y | wintab.PK_Z |
wintab.PK_NORMAL_PRESSURE | wintab.PK_TANGENT_PRESSURE |
wintab.PK_ORIENTATION)
context_info.lcPktMode = 0 # All absolute
wintab.PK_CHANGED | wintab.PK_CURSOR | wintab.PK_BUTTONS |
wintab.PK_X | wintab.PK_Y | wintab.PK_Z |
wintab.PK_NORMAL_PRESSURE | wintab.PK_TANGENT_PRESSURE |
wintab.PK_ORIENTATION) | _extension_masks
context_info.lcPktMode = 0 # All absolute (PACKETMODE)
self._context = lib.WTOpenW(window._hwnd, ctypes.byref(context_info), True)
if not self._context:
@ -145,10 +154,66 @@ class WintabTabletCanvas(TabletCanvas):
window._event_handlers[msg_base + wintab.WT_PACKET] = self._event_wt_packet
window._event_handlers[msg_base + wintab.WT_PROXIMITY] = self._event_wt_proximity
if _extension_masks:
window._event_handlers[msg_base + wintab.WT_PACKETEXT] = self._event_wt_packetext
self._current_cursor = None
self._pressure_scale = device.pressure_axis.get_scale()
self._pressure_bias = device.pressure_axis.get_bias()
self.express_keys = defaultdict(lambda: defaultdict(bool)) # [control_id][location_id]
self.express_key_ct = 0
self.touchrings = [] # Not currently implemented.
self.touchstrips = [] # Not currently implemented.
# Override test
for tablet_id in range(get_tablet_count()):
control_count = self.extension_get(wintab.WTX_EXPKEYS2, tablet_id, 0, 0,
wintab.TABLET_PROPERTY_CONTROLCOUNT)
self.express_key_ct = control_count
assert _debug(f"Controls Found: {control_count}")
if self.override_keys is True:
for control_id in range(control_count):
function_count = self.extension_get(wintab.WTX_EXPKEYS2, tablet_id, control_id, 0,
wintab.TABLET_PROPERTY_FUNCCOUNT)
for function_id in range(function_count):
self.extension_set(wintab.WTX_EXPKEYS2, tablet_id, control_id, function_id,
wintab.TABLET_PROPERTY_OVERRIDE, wintab.BOOL(True))
def extension_get(self, extension, tablet_id, control_id, function_id, property_id, value_type=wintab.UINT):
prop = wintab.EXTPROPERTY()
prop.version = 0
prop.tabletIndex = tablet_id
prop.controlIndex = control_id
prop.functionIndex = function_id
prop.propertyID = property_id
prop.reserved = 0
prop.dataSize = ctypes.sizeof(value_type)
success = lib.WTExtGet(self._context, extension, ctypes.byref(prop))
if success:
return ctypes.cast(prop.data, ctypes.POINTER(value_type)).contents.value
return 0
def extension_set(self, extension, tablet_id, control_id, function_id, property_id, value):
prop = wintab.EXTPROPERTY()
prop.version = 0
prop.tabletIndex = tablet_id
prop.controlIndex = control_id
prop.functionIndex = function_id
prop.propertyID = property_id
prop.reserved = 0
prop.dataSize = ctypes.sizeof(value)
prop.data[0] = value.value
success = lib.WTExtSet(self._context, extension, ctypes.byref(prop))
if success:
return True
return False
def close(self):
lib.WTClose(self._context)
self._context = None
@ -156,6 +221,9 @@ class WintabTabletCanvas(TabletCanvas):
del self.window._event_handlers[self.msg_base + wintab.WT_PACKET]
del self.window._event_handlers[self.msg_base + wintab.WT_PROXIMITY]
if _extension_masks:
del self.window._event_handlers[self.msg_base + wintab.WT_PACKETEXT]
def _set_current_cursor(self, cursor_type):
if self._current_cursor:
self.dispatch_event('on_leave', self._current_cursor)
@ -186,7 +254,25 @@ class WintabTabletCanvas(TabletCanvas):
if self._current_cursor is None:
self._set_current_cursor(packet.pkCursor)
self.dispatch_event('on_motion', self._current_cursor, x, y, pressure, 0., 0.)
self.dispatch_event('on_motion', self._current_cursor, x, y, pressure, 0., 0., packet.pkButtons)
@pyglet.window.win32.Win32EventHandler(0)
def _event_wt_packetext(self, msg, wParam, lParam):
packet = wintab.PACKETEXT()
if lib.WTPacket(lParam, wParam, ctypes.byref(packet)) == 0:
return
# Proper context exists in the packet, not the lParam.
if packet.pkBase.nContext == self._context:
if packet.pkExpKeys.nControl < self.express_key_ct:
current_state = self.express_keys[packet.pkExpKeys.nControl][packet.pkExpKeys.nLocation]
new_state = bool(packet.pkExpKeys.nState)
if current_state != new_state:
event_type = "on_express_key_press" if new_state else "on_express_key_release"
self.express_keys[packet.pkExpKeys.nControl][packet.pkExpKeys.nLocation] = new_state
self.dispatch_event(event_type, packet.pkExpKeys.nControl, packet.pkExpKeys.nLocation)
@pyglet.window.win32.Win32EventHandler(0)
def _event_wt_proximity(self, msg, wParam, lParam):
@ -205,6 +291,40 @@ class WintabTabletCanvas(TabletCanvas):
# can actually grab a cursor id.
self._current_cursor = None
def on_express_key_press(self, control_id: int, location_id: int):
"""An event called when an ExpressKey is pressed down.
:Parameters:
`control_id` : int
Zero-based index number given to the assigned key by the driver.
The same control_id may exist in multiple locations, which the location_id is used to differentiate.
`location_id: int
Zero-based index indicating side of tablet where control id was found.
Some tablets may have clusters of ExpressKey's on various areas of the tablet.
(0 = left, 1 = right, 2 = top, 3 = bottom, 4 = transducer).
:event:
"""
def on_express_key_release(self, control_id: int, location_id: int):
"""An event called when an ExpressKey is released.
:Parameters:
`control_id` : int
Zero-based index number given to the assigned key by the driver.
The same control_id may exist in multiple locations, which the location_id is used to differentiate.
`location_id: int
Zero-based index indicating side of tablet where control id was found.
Some tablets may have clusters of ExpressKey's on various areas of the tablet.
(0 = left, 1 = right, 2 = top, 3 = bottom, 4 = transducer).
:event:
"""
WintabTabletCanvas.register_event_type('on_express_key_press')
WintabTabletCanvas.register_event_type('on_express_key_release')
class WintabTabletCursor:
def __init__(self, device, index):
@ -244,11 +364,73 @@ def get_implementation_version():
return impl_version
def get_tablets(display=None):
# Require spec version 1.1 or greater
if get_spec_version() < 0x101:
return []
def extension_index(ext):
"""Check if a particular extension exists within the driver."""
exists = True
i = 0
index = 0xFFFFFFFF
while exists:
tag = wintab.UINT()
exists = lib.WTInfoW(wintab.WTI_EXTENSIONS + i, wintab.EXT_TAG, ctypes.byref(tag))
if tag.value == ext:
index = i
break
i += 1
if index != 0xFFFFFFFF:
return index
return None
def get_extension_masks():
"""Determine which extension support is available by getting the masks."""
masks = 0
tr_idx = extension_index(wintab.WTX_TOUCHRING)
if tr_idx is not None:
assert _debug("Touchring support found")
masks |= wtinfo_uint(wintab.WTI_EXTENSIONS + tr_idx, wintab.EXT_MASK)
else:
assert _debug("Touchring extension not found.")
ts_idx = extension_index(wintab.WTX_TOUCHSTRIP)
if ts_idx is not None:
assert _debug("Touchstrip support found.")
masks |= wtinfo_uint(wintab.WTI_EXTENSIONS + ts_idx, wintab.EXT_MASK)
else:
assert _debug("Touchstrip extension not found.")
expkeys_idx = extension_index(wintab.WTX_EXPKEYS2)
if expkeys_idx is not None:
assert _debug("ExpressKey support found.")
masks |= wtinfo_uint(wintab.WTI_EXTENSIONS + expkeys_idx, wintab.EXT_MASK)
else:
assert _debug("ExpressKey extension not found.")
return masks
def get_tablet_count():
"""Return just the number of current devices."""
spec_version = get_spec_version()
assert _debug(f"Wintab Version: {spec_version}")
if spec_version < 0x101:
return 0
n_devices = wtinfo_uint(wintab.WTI_INTERFACE, wintab.IFC_NDEVICES)
return n_devices
_extension_masks = None
def get_tablets(display=None):
# Require spec version 1.1 or greater
n_devices = get_tablet_count()
if not n_devices:
return []
devices = [WintabTablet(i) for i in range(n_devices)]
return devices

View File

@ -93,7 +93,7 @@ class XInputTabletCanvas(DeviceResponder, TabletCanvas):
x = e.x
y = self.window.height - e.y
pressure = e.axis_data[2] / float(cursor.max_pressure)
self.dispatch_event('on_motion', cursor, x, y, pressure, 0.0, 0.0)
self.dispatch_event('on_motion', cursor, x, y, pressure, 0.0, 0.0, 0.0)
def _proximity_in(self, e):
cursor = self._cursor_map.get(e.deviceid)

View File

@ -12,10 +12,14 @@ from pyglet.libs.win32.constants import CLSCTX_INPROC_SERVER
from pyglet.input.base import Device, Controller, Button, AbsoluteAxis, ControllerManager
lib = pyglet.lib.load_library('xinput1_4')
# TODO Add: xinput1_3 and xinput9_1_0 support
library_name = lib._name
for library_name in ['xinput1_4', 'xinput9_1_0', 'xinput1_3']:
try:
lib = ctypes.windll.LoadLibrary(library_name)
break
except OSError:
continue
else:
raise OSError('Could not import XInput')
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849
@ -159,35 +163,38 @@ class XINPUT_CAPABILITIES_EX(Structure):
]
XInputGetState = lib.XInputGetState
XInputGetState.restype = DWORD
XInputGetState.argtypes = [DWORD, POINTER(XINPUT_STATE)]
XInputGetStateEx = lib[100]
XInputGetStateEx.restype = DWORD
XInputGetStateEx.argtypes = [DWORD, POINTER(XINPUT_STATE)]
XInputSetState = lib.XInputSetState
XInputSetState.argtypes = [DWORD, POINTER(XINPUT_VIBRATION)]
XInputSetState.restype = DWORD
XInputGetCapabilities = lib.XInputGetCapabilities
XInputGetCapabilities.restype = DWORD
XInputGetCapabilities.argtypes = [DWORD, DWORD, POINTER(XINPUT_CAPABILITIES)]
# Hidden function
XInputGetCapabilitiesEx = lib[108]
XInputGetCapabilitiesEx.restype = DWORD
XInputGetCapabilitiesEx.argtypes = [DWORD, DWORD, DWORD, POINTER(XINPUT_CAPABILITIES_EX)]
# Only available for 1.4+
if library_name == "xinput1_4":
XInputGetBatteryInformation = lib.XInputGetBatteryInformation
XInputGetBatteryInformation.argtypes = [DWORD, BYTE, POINTER(XINPUT_BATTERY_INFORMATION)]
XInputGetBatteryInformation.restype = DWORD
XInputGetState = lib[100]
XInputGetState.restype = DWORD
XInputGetState.argtypes = [DWORD, POINTER(XINPUT_STATE)]
# Hidden function
XInputGetCapabilities = lib[108]
XInputGetCapabilities.restype = DWORD
XInputGetCapabilities.argtypes = [DWORD, DWORD, DWORD, POINTER(XINPUT_CAPABILITIES_EX)]
else:
XInputGetBatteryInformation = None
XInputGetState = lib.XInputGetState
XInputGetState.restype = DWORD
XInputGetState.argtypes = [DWORD, POINTER(XINPUT_STATE)]
XInputGetCapabilities = lib.XInputGetCapabilities
XInputGetCapabilities.restype = DWORD
XInputGetCapabilities.argtypes = [DWORD, DWORD, POINTER(XINPUT_CAPABILITIES)]
XInputSetState = lib.XInputSetState
XInputSetState.argtypes = [DWORD, POINTER(XINPUT_VIBRATION)]
XInputSetState.restype = DWORD
# wbemcli #################################################
BSTR = LPCWSTR
@ -337,7 +344,7 @@ def get_xinput_guids():
if sdl_guid not in guids_found:
guids_found.append(sdl_guid)
oleaut32.VariantClear(var)
oleaut32.VariantClear(byref(var))
return guids_found
@ -420,7 +427,7 @@ class XInputDeviceManager(EventDispatcher):
for i in range(XUSER_MAX_COUNT):
device = self.all_devices[i]
if XInputGetStateEx(i, byref(device.xinput_state)) == ERROR_DEVICE_NOT_CONNECTED:
if XInputGetState(i, byref(device.xinput_state)) == ERROR_DEVICE_NOT_CONNECTED:
continue
device.connected = True
self._connected_devices.add(i)
@ -436,6 +443,7 @@ class XInputDeviceManager(EventDispatcher):
with self._dev_lock:
return [dev for dev in self.all_devices if dev.connected]
# Threaded method:
def _get_state(self):
xuser_max_count = set(range(XUSER_MAX_COUNT)) # {0, 1, 2, 3}
polling_rate = self._polling_rate
@ -451,13 +459,14 @@ class XInputDeviceManager(EventDispatcher):
# Only check if not currently connected:
for i in xuser_max_count - self._connected_devices:
device = self.all_devices[i]
if XInputGetStateEx(i, byref(device.xinput_state)) == ERROR_DEVICE_NOT_CONNECTED:
if XInputGetState(i, byref(device.xinput_state)) == ERROR_DEVICE_NOT_CONNECTED:
continue
# Found a new connection:
device.connected = True
self._connected_devices.add(i)
self.dispatch_event('on_connect', device)
# Dispatch event in main thread:
pyglet.app.platform_event_loop.post_event(self, 'on_connect', device)
elapsed = 0.0
@ -465,14 +474,15 @@ class XInputDeviceManager(EventDispatcher):
# opened devices. Skip unopened devices to save CPU:
for i in self._connected_devices.copy():
device = self.all_devices[i]
result = XInputGetStateEx(i, byref(device.xinput_state))
result = XInputGetState(i, byref(device.xinput_state))
if result == ERROR_DEVICE_NOT_CONNECTED:
# Newly disconnected device:
if device.connected:
device.connected = False
self._connected_devices.remove(i)
self.dispatch_event('on_disconnect', device)
# Dispatch event in main thread:
pyglet.app.platform_event_loop.post_event(self, 'on_disconnect', device)
continue
elif result == ERROR_SUCCESS and device.is_open:

View File

@ -1,38 +1,16 @@
'''Wrapper for /usr/include/EGL/egl
"""Wrapper for /usr/include/EGL/egl
Generated with:
wrap.py -o lib_egl.py /usr/include/EGL/egl.h
Do not modify this file.
'''
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import ctypes
"""
from ctypes import *
import pyglet.lib
_lib = pyglet.lib.load_library('EGL')
_int_types = (c_int16, c_int32)
if hasattr(ctypes, 'c_int64'):
# Some builds of ctypes apparently do not have c_int64
# defined; it's a pretty good bet that these builds do not
# have 64-bit pointers.
_int_types += (ctypes.c_int64,)
for t in _int_types:
if sizeof(t) == sizeof(c_size_t):
c_ptrdiff_t = t
class c_void(Structure):
# c_void_p is a buggy return type, converting to int, so
# POINTER(None) == c_void_p is actually written as
# POINTER(c_void), so it can be treated as a real pointer.
_fields_ = [('dummy', c_int)]
__egl_h_ = 1 # /usr/include/EGL/egl.h:2
EGL_EGL_PROTOTYPES = 1 # /usr/include/EGL/egl.h:42
@ -117,12 +95,6 @@ PFNEGLGETCONFIGATTRIBPROC = CFUNCTYPE(EGLBoolean, EGLDisplay, EGLConfig, EGLint,
PFNEGLGETCONFIGSPROC = CFUNCTYPE(EGLBoolean, EGLDisplay, POINTER(EGLConfig), EGLint, POINTER(EGLint)) # /usr/include/EGL/egl.h:134
PFNEGLGETCURRENTDISPLAYPROC = CFUNCTYPE(EGLDisplay) # /usr/include/EGL/egl.h:135
PFNEGLGETCURRENTSURFACEPROC = CFUNCTYPE(EGLSurface, EGLint) # /usr/include/EGL/egl.h:136
class struct__XDisplay(Structure):
__slots__ = [
]
struct__XDisplay._fields_ = [
('_opaque_struct', c_int)
]
class struct__XDisplay(Structure):
__slots__ = [
@ -502,7 +474,7 @@ eglWaitSync.restype = EGLBoolean
eglWaitSync.argtypes = [EGLDisplay, EGLSync, EGLint]
__all__ = ['__egl_h_', 'EGL_EGL_PROTOTYPES', 'EGL_VERSION_1_0', 'EGLBoolean',
__all__ = ['__egl_h_', 'EGL_EGL_PROTOTYPES', 'EGL_VERSION_1_0', 'EGLBoolean', 'EGLint',
'EGLDisplay', 'EGLConfig', 'EGLSurface', 'EGLContext',
'__eglMustCastToProperFunctionPointerType', 'EGL_ALPHA_SIZE',
'EGL_BAD_ACCESS', 'EGL_BAD_ALLOC', 'EGL_BAD_ATTRIBUTE', 'EGL_BAD_CONFIG',

View File

@ -2,9 +2,16 @@ from ctypes import *
from pyglet.libs.egl import egl
from pyglet.libs.egl.lib import link_EGL as _link_function
EGL_PLATFORM_GBM_MESA = 12759
EGL_PLATFORM_DEVICE_EXT = 12607
EGLDeviceEXT = POINTER(None)
eglGetPlatformDisplayEXT = _link_function('eglGetPlatformDisplayEXT', egl.EGLDisplay, [egl.EGLenum, POINTER(None), POINTER(egl.EGLint)], None)
eglCreatePlatformWindowSurfaceEXT = _link_function('eglCreatePlatformWindowSurfaceEXT', egl.EGLSurface, [egl.EGLDisplay, egl.EGLConfig, POINTER(None), POINTER(egl.EGLAttrib)], None)
eglQueryDevicesEXT = _link_function('eglQueryDevicesEXT', egl.EGLBoolean, [egl.EGLint, POINTER(EGLDeviceEXT), POINTER(egl.EGLint)], None)
__all__ = ['EGL_PLATFORM_DEVICE_EXT', 'EGLDeviceEXT', 'eglGetPlatformDisplayEXT', 'eglQueryDevicesEXT']
__all__ = ['EGL_PLATFORM_DEVICE_EXT', 'EGL_PLATFORM_GBM_MESA',
'EGLDeviceEXT', 'eglGetPlatformDisplayEXT', 'eglCreatePlatformWindowSurfaceEXT',
'eglQueryDevicesEXT']

View File

@ -35,6 +35,7 @@
import atexit
import struct
import warnings
import pyglet
from . import com

View File

@ -34,17 +34,13 @@
# ----------------------------------------------------------------------------
import ctypes
from ctypes.wintypes import HANDLE, BYTE, HWND, BOOL, UINT, LONG, WORD, DWORD, WCHAR, LPVOID
lib = ctypes.windll.wintab32
LONG = ctypes.c_long
BOOL = ctypes.c_int
UINT = ctypes.c_uint
WORD = ctypes.c_uint16
DWORD = ctypes.c_uint32
WCHAR = ctypes.c_wchar
FIX32 = DWORD
WTPKT = DWORD
HCTX = HANDLE # CONTEXT HANDLE
LCNAMELEN = 40
@ -119,6 +115,56 @@ class LOGCONTEXT(ctypes.Structure):
)
class TILT(ctypes.Structure): # 1.1
_fields_ = (
('tiltX', ctypes.c_int),
('tiltY', ctypes.c_int),
)
class EXTENSIONBASE(ctypes.Structure): # 1.4
_fields_ = (
('nContext', HCTX), # Specifies the Wintab context to which these properties apply.
('nStatus', UINT), # Status of setting/getting properties.
('nTime', DWORD), # Timestamp applied to property transaction.
('nSerialNumber', UINT), # Reserved - not use
)
class EXPKEYSDATA(ctypes.Structure): # 1.4
_fields_ = (
('nTablet', BYTE), # Tablet index where control is found.
('nControl', BYTE), # Zero-based control index.
('nLocation', BYTE), # Zero-based index indicating side of tablet where control found (0 = left, 1 = right).
('nReserved', BYTE), # Reserved - not used
('nState', DWORD) # Indicates Express Key button press (1 = pressed, 0 = released)
)
class SLIDERDATA(ctypes.Structure): # 1.4
_fields_ = (
('nTablet', BYTE), # Tablet index where control is found.
('nControl', BYTE), # Zero-based control index.
('nMode', BYTE), # Zero-based current active mode of control. Mode selected by control's toggle button.
('nReserved', BYTE), # Reserved - not used
('nPosition', DWORD) # An integer representing the position of the user's finger on the control.
# When there is no finger on the control, this value is negative.
)
class EXTPROPERTY(ctypes.Structure): # 1.4
_fields_ = (
('version', BYTE), # Structure version, 0 for now
('tabletIndex', BYTE), # 0-based index for tablet
('controlIndex', BYTE), # 0-based index for control
('functionIndex', BYTE), # 0-based index for control's sub-function
('propertyID', WORD), # property ID
('reserved', WORD), # DWORD-alignment filler
('dataSize', DWORD), # number of bytes in data[] buffer
('data', BYTE * 1), # raw data
)
# Custom packet format with fields
# PK_CHANGED
# PK_CURSOR
@ -143,6 +189,15 @@ class PACKET(ctypes.Structure):
)
class PACKETEXT(ctypes.Structure):
_fields_ = (
('pkBase', EXTENSIONBASE), # Extension control properties common to all control types.
('pkExpKeys', EXPKEYSDATA), # Extension data for one Express Key.
('pkTouchStrip', SLIDERDATA), # Extension data for one Touch Strip.
('pkTouchRing', SLIDERDATA) # Extension data for one Touch Ring.
)
PK_CONTEXT = 0x0001 # reporting context
PK_STATUS = 0x0002 # status bits
PK_TIME = 0x0004 # time stamp
@ -174,6 +229,7 @@ WT_CTXOVERLAP = 4
WT_PROXIMITY = 5
WT_INFOCHANGE = 6
WT_CSRCHANGE = 7
WT_PACKETEXT = 8
# system button assignment values
SBN_NONE = 0x00
@ -370,4 +426,47 @@ WTX_FKEYS = 1 # Function keys
WTX_TILT = 2 # Raw Cartesian tilt; 1.1
WTX_CSRMASK = 3 # select input by cursor type; 1.1
WTX_XBTNMASK = 4 # Extended button mask; 1.1
WTX_EXPKEYS = 5 # ExpressKeys; 1.3
WTX_EXPKEYS = 5 # ExpressKeys; 1.3 - DEPRECATED USE 2
WTX_TOUCHSTRIP = 6 # TouchStrips; 1.4
WTX_TOUCHRING = 7 # TouchRings; 1.4
WTX_EXPKEYS2 = 8 # ExpressKeys; 1.4
TABLET_PROPERTY_CONTROLCOUNT = 0 # UINT32: number of physical controls on tablet
TABLET_PROPERTY_FUNCCOUNT = 1 # UINT32: number of functions of control
TABLET_PROPERTY_AVAILABLE = 2 # BOOL: control/mode is available for override
TABLET_PROPERTY_MIN = 3 # UINT32: minimum value
TABLET_PROPERTY_MAX = 4 # UINT32: maximum value
TABLET_PROPERTY_OVERRIDE = 5 # BOOL: control is overridden
TABLET_PROPERTY_OVERRIDE_NAME = 6 # UTF-8: Displayable name when control is overridden
TABLET_PROPERTY_OVERRIDE_ICON = 7 # Image: Icon to show when control is overridden
TABLET_PROPERTY_ICON_WIDTH = 8 # UINT32: Pixel width of icon display
TABLET_PROPERTY_ICON_HEIGHT = 9 # UINT32: Pixel height of icon display
TABLET_PROPERTY_ICON_FORMAT = 10 # UINT32: UINT32: Pixel format of icon display (see TABLET_ICON_FMT_*)
TABLET_PROPERTY_LOCATION = 11 # UINT32: Physical location of control (see TABLET_LOC_*)
TABLET_LOC_LEFT = 0
TABLET_LOC_RIGHT = 1
TABLET_LOC_TOP = 2
TABLET_LOC_BOTTOM = 3
TABLET_LOC_TRANSDUCER = 4
lib.WTOpenW.restype = HCTX
lib.WTOpenW.argtypes = [HWND, ctypes.POINTER(LOGCONTEXT), BOOL]
lib.WTClose.restype = BOOL
lib.WTClose.argtypes = [HCTX]
lib.WTInfoW.restype = UINT
lib.WTInfoW.argtypes = [UINT, UINT, LPVOID]
lib.WTPacket.restype = BOOL
lib.WTPacket.argtypes = [HCTX, UINT, LPVOID]
lib.WTGetW.restype = BOOL
lib.WTGetW.argtypes = [HCTX, BOOL]
lib.WTExtGet.restype = BOOL
lib.WTExtGet.argtypes = [HCTX, UINT, LPVOID]
lib.WTExtSet.restype = BOOL
lib.WTExtSet.argtypes = [HCTX, UINT, LPVOID]

View File

@ -113,9 +113,15 @@ class Vec2:
else:
return self.__add__(other)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __ne__(self, other):
return self.x != other.x or self.y != other.y
@staticmethod
def from_polar(mag, angle):
"""Create a new vector from the given polar coodinates.
"""Create a new vector from the given polar coordinates.
:parameters:
`mag` : int or float :
@ -361,6 +367,12 @@ class Vec3:
else:
return self.__add__(other)
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.z == other.z
def __ne__(self, other):
return self.x != other.x or self.y != other.y or self.z != other.z
def from_magnitude(self, magnitude):
"""Create a new Vector of the given magnitude by normalizing,
then scaling the vector. The rotation remains unchanged.
@ -560,6 +572,12 @@ class Vec4:
else:
return self.__add__(other)
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.z == other.z and self.w == other.w
def __ne__(self, other):
return self.x != other.x or self.y != other.y or self.z != other.z or self.w != other.w
def lerp(self, other, alpha):
return Vec4(self.x + (alpha * (other.x - self.x)),
self.y + (alpha * (other.y - self.y)),
@ -964,7 +982,7 @@ class Mat4(tuple):
c2 = other[2::4]
c3 = other[3::4]
# Multiply and sum rows * colums:
# Multiply and sum rows * columns:
return Mat4((sum(map(_mul, r0, c0)),
sum(map(_mul, r0, c1)),
sum(map(_mul, r0, c2)),

View File

@ -85,7 +85,7 @@ def load_material_library(filename):
name = values[1]
elif name is None:
raise ModelDecodeException('Expected "newmtl" in '.format(filename))
raise ModelDecodeException(f'Expected "newmtl" in {filename}')
try:
if values[0] == 'Kd':
@ -97,7 +97,8 @@ def load_material_library(filename):
elif values[0] == 'Ke':
emission = list(map(float, values[1:]))
elif values[0] == 'Ns':
shininess = float(values[1])
shininess = float(values[1]) # Blender exports 1~1000
shininess = (shininess * 128) / 1000 # Normalize to 1~128 for OpenGL
elif values[0] == 'd':
opacity = float(values[1])
elif values[0] == 'map_Kd':

View File

@ -381,7 +381,8 @@ class _ShapeBase:
class Arc(_ShapeBase):
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0,
closed=False, color=(255, 255, 255), batch=None, group=None):
closed=False, color=(255, 255, 255), opacity=255, batch=None,
group=None):
"""Create an Arc.
The Arc's anchor point (x, y) defaults to its center.
@ -409,6 +410,9 @@ class Arc(_ShapeBase):
`color` : (int, int, int)
The RGB color of the circle, specified as a tuple of
three ints in the range of 0-255.
`opacity` : int
How opaque the arc is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the circle to.
`group` : `~pyglet.graphics.Group`
@ -421,6 +425,7 @@ class Arc(_ShapeBase):
self._num_verts = self._segments * 2 + (2 if closed else 0)
self._rgb = color
self._opacity = opacity
self._angle = angle
self._start_angle = start_angle
self._closed = closed
@ -489,7 +494,8 @@ class Arc(_ShapeBase):
class Circle(_ShapeBase):
def __init__(self, x, y, radius, segments=None, color=(255, 255, 255), batch=None, group=None):
def __init__(self, x, y, radius, segments=None, color=(255, 255, 255), opacity=255,
batch=None, group=None):
"""Create a circle.
The circle's anchor point (x, y) defaults to the center of the circle.
@ -509,6 +515,9 @@ class Circle(_ShapeBase):
`color` : (int, int, int)
The RGB color of the circle, specified as a tuple of
three ints in the range of 0-255.
`opacity` : int
How opaque the circle is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the circle to.
`group` : `~pyglet.graphics.Group`
@ -519,6 +528,7 @@ class Circle(_ShapeBase):
self._radius = radius
self._segments = segments or max(14, int(radius / 1.25))
self._rgb = color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()
@ -567,7 +577,8 @@ class Circle(_ShapeBase):
class Ellipse(_ShapeBase):
def __init__(self, x, y, a, b, color=(255, 255, 255), batch=None, group=None):
def __init__(self, x, y, a, b, color=(255, 255, 255), opacity=255,
batch=None, group=None):
"""Create an ellipse.
The ellipse's anchor point (x, y) defaults to the center of the ellipse.
@ -584,6 +595,9 @@ class Ellipse(_ShapeBase):
`color` : (int, int, int)
The RGB color of the ellipse. specify as a tuple of
three ints in the range of 0~255.
`opacity` : int
How opaque the ellipse is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the circle to.
`group` : `~pyglet.graphics.Group`
@ -594,6 +608,7 @@ class Ellipse(_ShapeBase):
self._a = a
self._b = b
self._rgb = color
self._opacity = opacity
self._rotation = 0
self._segments = int(max(a, b) / 1.25)
self._num_verts = self._segments * 2
@ -685,7 +700,7 @@ class Ellipse(_ShapeBase):
class Sector(_ShapeBase):
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0,
color=(255, 255, 255), batch=None, group=None):
color=(255, 255, 255), opacity=255, batch=None, group=None):
"""Create a Sector of a circle.
The sector's anchor point (x, y) defaults to the center of the circle.
@ -710,6 +725,9 @@ class Sector(_ShapeBase):
`color` : (int, int, int)
The RGB color of the sector, specified as a tuple of
three ints in the range of 0-255.
`opacity` : int
How opaque the sector is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the sector to.
`group` : `~pyglet.graphics.Group`
@ -721,6 +739,7 @@ class Sector(_ShapeBase):
self._segments = segments or max(14, int(radius / 1.25))
self._rgb = color
self._opacity = opacity
self._angle = angle
self._start_angle = start_angle
self._rotation = 0
@ -815,7 +834,8 @@ class Sector(_ShapeBase):
class Line(_ShapeBase):
def __init__(self, x, y, x2, y2, width=1, color=(255, 255, 255), batch=None, group=None):
def __init__(self, x, y, x2, y2, width=1, color=(255, 255, 255), opacity=255,
batch=None, group=None):
"""Create a line.
The line's anchor point defaults to the center of the line's
@ -835,6 +855,9 @@ class Line(_ShapeBase):
`color` : (int, int, int)
The RGB color of the line, specified as a tuple of
three ints in the range of 0-255.
`opacity` : int
How opaque the line is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the line to.
`group` : `~pyglet.graphics.Group`
@ -848,6 +871,7 @@ class Line(_ShapeBase):
self._width = width
self._rotation = math.degrees(math.atan2(y2 - y, x2 - x))
self._rgb = color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()
@ -933,7 +957,8 @@ class Line(_ShapeBase):
class Rectangle(_ShapeBase):
def __init__(self, x, y, width, height, color=(255, 255, 255), batch=None, group=None):
def __init__(self, x, y, width, height, color=(255, 255, 255), opacity=255,
batch=None, group=None):
"""Create a rectangle or square.
The rectangle's anchor point defaults to the (x, y) coordinates,
@ -951,6 +976,9 @@ class Rectangle(_ShapeBase):
`color` : (int, int, int)
The RGB color of the rectangle, specified as
a tuple of three ints in the range of 0-255.
`opacity` : int
How opaque the rectangle is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the rectangle to.
`group` : `~pyglet.graphics.Group`
@ -962,6 +990,7 @@ class Rectangle(_ShapeBase):
self._height = height
self._rotation = 0
self._rgb = color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()
@ -1037,7 +1066,7 @@ class Rectangle(_ShapeBase):
class BorderedRectangle(_ShapeBase):
def __init__(self, x, y, width, height, border=1, color=(255, 255, 255),
border_color=(100, 100, 100), batch=None, group=None):
border_color=(100, 100, 100), opacity=255, batch=None, group=None):
"""Create a rectangle or square.
The rectangle's anchor point defaults to the (x, y) coordinates,
@ -1060,6 +1089,10 @@ class BorderedRectangle(_ShapeBase):
`border_color` : (int, int, int)
The RGB color of the rectangle's border, specified as
a tuple of three ints in the range of 0-255.
`opacity` : int
How opaque the rectangle is. The default of 255 is fully
visible. 0 is transparent. This affects the entire shape,
not only the fill color.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the rectangle to.
`group` : `~pyglet.graphics.Group`
@ -1073,6 +1106,7 @@ class BorderedRectangle(_ShapeBase):
self._border = border
self._rgb = color
self._brgb = border_color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()
@ -1176,7 +1210,8 @@ class BorderedRectangle(_ShapeBase):
class Triangle(_ShapeBase):
def __init__(self, x, y, x2, y2, x3, y3, color=(255, 255, 255), batch=None, group=None):
def __init__(self, x, y, x2, y2, x3, y3, color=(255, 255, 255), opacity=255,
batch=None, group=None):
"""Create a triangle.
The triangle's anchor point defaults to the first vertex point.
@ -1197,6 +1232,9 @@ class Triangle(_ShapeBase):
`color` : (int, int, int)
The RGB color of the triangle, specified as
a tuple of three ints in the range of 0-255.
`opacity` : int
How opaque the triangle is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the triangle to.
`group` : `~pyglet.graphics.Group`
@ -1210,6 +1248,7 @@ class Triangle(_ShapeBase):
self._y3 = y3
self._rotation = 0
self._rgb = color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()
@ -1316,7 +1355,7 @@ class Triangle(_ShapeBase):
class Star(_ShapeBase):
def __init__(self, x, y, outer_radius, inner_radius, num_spikes, rotation=0,
color=(255, 255, 255), batch=None, group=None) -> None:
color=(255, 255, 255), opacity=255, batch=None, group=None) -> None:
"""Create a star.
The star's anchor point (x, y) defaults to the center of the star.
@ -1339,6 +1378,9 @@ class Star(_ShapeBase):
`color` : (int, int, int)
The RGB color of the star, specified as
a tuple of three ints in the range of 0-255.
`opacity` : int
How opaque the star is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the star to.
`group` : `~pyglet.graphics.Group`
@ -1350,6 +1392,7 @@ class Star(_ShapeBase):
self._inner_radius = inner_radius
self._num_spikes = num_spikes
self._rgb = color
self._opacity = opacity
self._rotation = rotation
program = get_default_shader()
@ -1437,7 +1480,7 @@ class Star(_ShapeBase):
class Polygon(_ShapeBase):
def __init__(self, *coordinates, color=(255, 255, 255), batch=None, group=None):
def __init__(self, *coordinates, color=(255, 255, 255), opacity=255, batch=None, group=None):
"""Create a convex polygon.
The polygon's anchor point defaults to the first vertex point.
@ -1448,6 +1491,9 @@ class Polygon(_ShapeBase):
`color` : (int, int, int)
The RGB color of the polygon, specified as
a tuple of three ints in the range of 0-255.
`opacity` : int
How opaque the polygon is. The default of 255 is fully
visible. 0 is transparent.
`batch` : `~pyglet.graphics.Batch`
Optional batch to add the polygon to.
`group` : `~pyglet.graphics.Group`
@ -1460,6 +1506,7 @@ class Polygon(_ShapeBase):
self._rotation = 0
self._rgb = color
self._opacity = opacity
program = get_default_shader()
self._batch = batch or Batch()

View File

@ -73,9 +73,11 @@ creating scrollable layouts.
.. versionadded:: 1.1
"""
import os.path
from os.path import dirname as _dirname
from os.path import splitext as _splitext
import pyglet
from pyglet.text import layout, document, caret
@ -130,7 +132,7 @@ def get_decoder(filename, mimetype=None):
:rtype: `DocumentDecoder`
"""
if mimetype is None:
_, ext = os.path.splitext(filename)
_, ext = _splitext(filename)
if ext.lower() in ('.htm', '.html', '.xhtml'):
mimetype = 'text/html'
else:
@ -176,7 +178,7 @@ def load(filename, file=None, mimetype=None):
if hasattr(file_contents, "decode"):
file_contents = file_contents.decode()
location = pyglet.resource.FileLocation(os.path.dirname(filename))
location = pyglet.resource.FileLocation(_dirname(filename))
return decoder.decode(file_contents, location)
@ -265,11 +267,7 @@ class DocumentLabel(layout.TextLayout):
Optional graphics group to use.
"""
super(DocumentLabel, self).__init__(document,
width=width, height=height,
multiline=multiline,
dpi=dpi, batch=batch, group=group)
super().__init__(document, width, height, multiline, dpi, batch, group)
self._x = x
self._y = y
self._anchor_x = anchor_x
@ -347,8 +345,7 @@ class DocumentLabel(layout.TextLayout):
@font_size.setter
def font_size(self, font_size):
self.document.set_style(0, len(self.document.text),
{'font_size': font_size})
self.document.set_style(0, len(self.document.text), {'font_size': font_size})
@property
def bold(self):
@ -360,8 +357,7 @@ class DocumentLabel(layout.TextLayout):
@bold.setter
def bold(self, bold):
self.document.set_style(0, len(self.document.text),
{'bold': bold})
self.document.set_style(0, len(self.document.text), {'bold': bold})
@property
def italic(self):
@ -373,8 +369,7 @@ class DocumentLabel(layout.TextLayout):
@italic.setter
def italic(self, italic):
self.document.set_style(0, len(self.document.text),
{'italic': italic})
self.document.set_style(0, len(self.document.text), {'italic': italic})
def get_style(self, name):
"""Get a document style value by name.
@ -404,6 +399,9 @@ class DocumentLabel(layout.TextLayout):
"""
self.document.set_style(0, len(self.document.text), {name: value})
def __del__(self):
self.delete()
class Label(DocumentLabel):
"""Plain text label.
@ -463,10 +461,8 @@ class Label(DocumentLabel):
Optional graphics group to use.
"""
document = decode_text(text)
super(Label, self).__init__(document, x, y, width, height,
anchor_x, anchor_y,
multiline, dpi, batch, group)
doc = decode_text(text)
super().__init__(doc, x, y, width, height, anchor_x, anchor_y, multiline, dpi, batch, group)
self.document.set_style(0, len(self.document.text), {
'font_name': font_name,
@ -525,10 +521,8 @@ class HTMLLabel(DocumentLabel):
"""
self._text = text
self._location = location
document = decode_html(text, location)
super(HTMLLabel, self).__init__(document, x, y, width, height,
anchor_x, anchor_y,
multiline, dpi, batch, group)
doc = decode_html(text, location)
super().__init__(doc, x, y, width, height, anchor_x, anchor_y, multiline, dpi, batch, group)
@property
def text(self):
@ -542,4 +536,3 @@ class HTMLLabel(DocumentLabel):
def text(self, text):
self._text = text
self.document = decode_html(text, self._location)

View File

@ -1851,7 +1851,7 @@ class FPSDisplay:
#: :type: float
update_period = 0.25
def __init__(self, window, color=(127, 127, 127, 127), samples=60):
def __init__(self, window, color=(127, 127, 127, 127), samples=240):
from time import time
from statistics import mean
from collections import deque
@ -1879,19 +1879,7 @@ class FPSDisplay:
if self._elapsed >= self.update_period:
self._elapsed = 0
self._set_fps_text(1 / self._mean(self._delta_times))
def _set_fps_text(self, fps):
"""Set the label text for the given FPS estimation.
Called by `update` every `update_period` seconds.
:Parameters:
`fps` : float
Estimated framerate of the window.
"""
self.label.text = '%.2f' % fps
self.label.text = f"{1 / self._mean(self._delta_times):.2f}"
def draw(self):
"""Draw the label."""

View File

@ -269,18 +269,14 @@ class Win32Window(BaseWindow):
GWL_EXSTYLE,
self._ex_ws_style)
if self._fullscreen:
hwnd_after = HWND_TOPMOST
else:
hwnd_after = HWND_NOTOPMOST
# Position and size window
if self._fullscreen:
hwnd_after = HWND_TOPMOST if self.style == "overlay" else HWND_NOTOPMOST
_user32.SetWindowPos(self._hwnd, hwnd_after,
self._screen.x, self._screen.y, width, height, SWP_FRAMECHANGED)
elif False: # TODO location not in pyglet API
x, y = self._client_to_window_pos(*factory.get_location())
_user32.SetWindowPos(self._hwnd, hwnd_after,
_user32.SetWindowPos(self._hwnd, HWND_NOTOPMOST,
x, y, width, height, SWP_FRAMECHANGED)
elif self.style == 'transparent' or self.style == "overlay":
_user32.SetLayeredWindowAttributes(self._hwnd, 0, 254, LWA_ALPHA)
@ -288,7 +284,7 @@ class Win32Window(BaseWindow):
_user32.SetWindowPos(self._hwnd, HWND_TOPMOST, 0,
0, width, height, SWP_NOMOVE | SWP_NOSIZE)
else:
_user32.SetWindowPos(self._hwnd, hwnd_after,
_user32.SetWindowPos(self._hwnd, HWND_NOTOPMOST,
0, 0, width, height, SWP_NOMOVE | SWP_FRAMECHANGED)
self._update_view_location(self._width, self._height)
@ -429,7 +425,7 @@ class Win32Window(BaseWindow):
def set_visible(self, visible=True):
if visible:
insertAfter = HWND_TOPMOST if self._fullscreen else HWND_TOP
insertAfter = HWND_TOP
_user32.SetWindowPos(self._hwnd, insertAfter, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW)
self.dispatch_event('on_resize', self._width, self._height)