From 5667dacda6f67d66204d07b50474af3653edc113 Mon Sep 17 00:00:00 2001 From: shenjack <3695888@qq.com> Date: Sat, 16 Jul 2022 20:20:23 +0800 Subject: [PATCH] pyglet update --- Difficult_Rocket/client/__init__.py | 12 +- Difficult_Rocket/utils/cprint/py_cprint.c | 46 +++-- Difficult_Rocket/utils/cprint/py_cprint.py | 15 +- Difficult_Rocket/utils/thread.py | 8 +- libs/msdnicrosoft_logger/shenjack.py | 156 ++++++++++++---- libs/pyglet/__init__.py | 4 +- libs/pyglet/app/base.py | 2 +- libs/pyglet/clock.py | 8 +- libs/pyglet/font/__init__.py | 30 ++- libs/pyglet/gl/lib_wgl.py | 3 +- libs/pyglet/graphics/__init__.py | 9 +- libs/pyglet/graphics/shader.py | 165 +++++++++-------- libs/pyglet/graphics/vertexbuffer.py | 66 +++---- libs/pyglet/graphics/vertexdomain.py | 9 +- libs/pyglet/gui/widgets.py | 2 +- libs/pyglet/input/base.py | 7 +- libs/pyglet/input/controller_db.py | 17 +- libs/pyglet/input/evdev.py | 4 +- libs/pyglet/input/wintab.py | 206 +++++++++++++++++++-- libs/pyglet/input/x11_xinput_tablet.py | 2 +- libs/pyglet/input/xinput.py | 72 +++---- libs/pyglet/libs/egl/egl.py | 34 +--- libs/pyglet/libs/egl/eglext.py | 9 +- libs/pyglet/libs/win32/__init__.py | 1 + libs/pyglet/libs/win32/libwintab.py | 113 ++++++++++- libs/pyglet/math.py | 22 ++- libs/pyglet/model/codecs/obj.py | 5 +- libs/pyglet/shapes.py | 67 ++++++- libs/pyglet/text/__init__.py | 39 ++-- libs/pyglet/window/__init__.py | 16 +- libs/pyglet/window/win32/__init__.py | 12 +- 31 files changed, 814 insertions(+), 347 deletions(-) diff --git a/Difficult_Rocket/client/__init__.py b/Difficult_Rocket/client/__init__.py index 058bca3..d2716f1 100644 --- a/Difficult_Rocket/client/__init__.py +++ b/Difficult_Rocket/client/__init__.py @@ -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 diff --git a/Difficult_Rocket/utils/cprint/py_cprint.c b/Difficult_Rocket/utils/cprint/py_cprint.c index 6786de2..0f8f3ad 100644 --- a/Difficult_Rocket/utils/cprint/py_cprint.c +++ b/Difficult_Rocket/utils/cprint/py_cprint.c @@ -7,19 +7,43 @@ #include #include + + +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); diff --git a/Difficult_Rocket/utils/cprint/py_cprint.py b/Difficult_Rocket/utils/cprint/py_cprint.py index 1642aa1..6702850 100644 --- a/Difficult_Rocket/utils/cprint/py_cprint.py +++ b/Difficult_Rocket/utils/cprint/py_cprint.py @@ -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() diff --git a/Difficult_Rocket/utils/thread.py b/Difficult_Rocket/utils/thread.py index 262281f..957cfd0 100644 --- a/Difficult_Rocket/utils/thread.py +++ b/Difficult_Rocket/utils/thread.py @@ -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): diff --git a/libs/msdnicrosoft_logger/shenjack.py b/libs/msdnicrosoft_logger/shenjack.py index dc2c628..408b0da 100644 --- a/libs/msdnicrosoft_logger/shenjack.py +++ b/libs/msdnicrosoft_logger/shenjack.py @@ -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') + ... diff --git a/libs/pyglet/__init__.py b/libs/pyglet/__init__.py index cde9f1f..be70285 100644 --- a/libs/pyglet/__init__.py +++ b/libs/pyglet/__init__.py @@ -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, diff --git a/libs/pyglet/app/base.py b/libs/pyglet/app/base.py index 79a5cbf..2a5dee0 100644 --- a/libs/pyglet/app/base.py +++ b/libs/pyglet/app/base.py @@ -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): diff --git a/libs/pyglet/clock.py b/libs/pyglet/clock.py index 8672d40..01fe7bc 100644 --- a/libs/pyglet/clock.py +++ b/libs/pyglet/clock.py @@ -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 diff --git a/libs/pyglet/font/__init__.py b/libs/pyglet/font/__init__.py index 09c6b85..f77f09e 100644 --- a/libs/pyglet/font/__init__.py +++ b/libs/pyglet/font/__init__.py @@ -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) diff --git a/libs/pyglet/gl/lib_wgl.py b/libs/pyglet/gl/lib_wgl.py index f401cb8..d0f9089 100644 --- a/libs/pyglet/gl/lib_wgl.py +++ b/libs/pyglet/gl/lib_wgl.py @@ -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: diff --git a/libs/pyglet/graphics/__init__.py b/libs/pyglet/graphics/__init__.py index f402550..b4bcbee 100644 --- a/libs/pyglet/graphics/__init__.py +++ b/libs/pyglet/graphics/__init__.py @@ -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: diff --git a/libs/pyglet/graphics/shader.py b/libs/pyglet/graphics/shader.py index 881cb22..80d40b1 100644 --- a/libs/pyglet/graphics/shader.py +++ b/libs/pyglet/graphics/shader.py @@ -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) diff --git a/libs/pyglet/graphics/vertexbuffer.py b/libs/pyglet/graphics/vertexbuffer.py index 9a7e7c3..b503f0e 100644 --- a/libs/pyglet/graphics/vertexbuffer.py +++ b/libs/pyglet/graphics/vertexbuffer.py @@ -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 diff --git a/libs/pyglet/graphics/vertexdomain.py b/libs/pyglet/graphics/vertexdomain.py index d93d2ea..64317fc 100644 --- a/libs/pyglet/graphics/vertexdomain.py +++ b/libs/pyglet/graphics/vertexdomain.py @@ -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 + diff --git a/libs/pyglet/gui/widgets.py b/libs/pyglet/gui/widgets.py index 77e4bcd..04723e0 100644 --- a/libs/pyglet/gui/widgets.py +++ b/libs/pyglet/gui/widgets.py @@ -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. diff --git a/libs/pyglet/input/base.py b/libs/pyglet/input/base.py index 079c1bd..4b10f80 100644 --- a/libs/pyglet/input/base.py +++ b/libs/pyglet/input/base.py @@ -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: """ diff --git a/libs/pyglet/input/controller_db.py b/libs/pyglet/input/controller_db.py index 923c8a4..34b03ee 100644 --- a/libs/pyglet/input/controller_db.py +++ b/libs/pyglet/input/controller_db.py @@ -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,", diff --git a/libs/pyglet/input/evdev.py b/libs/pyglet/input/evdev.py index 238e17c..d82b7e2 100644 --- a/libs/pyglet/input/evdev.py +++ b/libs/pyglet/input/evdev.py @@ -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) diff --git a/libs/pyglet/input/wintab.py b/libs/pyglet/input/wintab.py index 3b633b4..a4f3ed5 100644 --- a/libs/pyglet/input/wintab.py +++ b/libs/pyglet/input/wintab.py @@ -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 diff --git a/libs/pyglet/input/x11_xinput_tablet.py b/libs/pyglet/input/x11_xinput_tablet.py index c4f093c..210da85 100644 --- a/libs/pyglet/input/x11_xinput_tablet.py +++ b/libs/pyglet/input/x11_xinput_tablet.py @@ -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) diff --git a/libs/pyglet/input/xinput.py b/libs/pyglet/input/xinput.py index 257e8c6..e607ca9 100644 --- a/libs/pyglet/input/xinput.py +++ b/libs/pyglet/input/xinput.py @@ -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: diff --git a/libs/pyglet/libs/egl/egl.py b/libs/pyglet/libs/egl/egl.py index 19f9d63..2bd4808 100644 --- a/libs/pyglet/libs/egl/egl.py +++ b/libs/pyglet/libs/egl/egl.py @@ -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', diff --git a/libs/pyglet/libs/egl/eglext.py b/libs/pyglet/libs/egl/eglext.py index fd93bc8..718182b 100644 --- a/libs/pyglet/libs/egl/eglext.py +++ b/libs/pyglet/libs/egl/eglext.py @@ -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'] diff --git a/libs/pyglet/libs/win32/__init__.py b/libs/pyglet/libs/win32/__init__.py index 9abea45..bdf5183 100644 --- a/libs/pyglet/libs/win32/__init__.py +++ b/libs/pyglet/libs/win32/__init__.py @@ -35,6 +35,7 @@ import atexit import struct +import warnings import pyglet from . import com diff --git a/libs/pyglet/libs/win32/libwintab.py b/libs/pyglet/libs/win32/libwintab.py index e001f70..5dae8f7 100644 --- a/libs/pyglet/libs/win32/libwintab.py +++ b/libs/pyglet/libs/win32/libwintab.py @@ -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] diff --git a/libs/pyglet/math.py b/libs/pyglet/math.py index 5cf7adf..ca098c4 100644 --- a/libs/pyglet/math.py +++ b/libs/pyglet/math.py @@ -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)), diff --git a/libs/pyglet/model/codecs/obj.py b/libs/pyglet/model/codecs/obj.py index 87819fc..cd91e82 100644 --- a/libs/pyglet/model/codecs/obj.py +++ b/libs/pyglet/model/codecs/obj.py @@ -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': diff --git a/libs/pyglet/shapes.py b/libs/pyglet/shapes.py index 9c61729..bd8e876 100644 --- a/libs/pyglet/shapes.py +++ b/libs/pyglet/shapes.py @@ -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() diff --git a/libs/pyglet/text/__init__.py b/libs/pyglet/text/__init__.py index 77f827e..8271a02 100644 --- a/libs/pyglet/text/__init__.py +++ b/libs/pyglet/text/__init__.py @@ -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) - diff --git a/libs/pyglet/window/__init__.py b/libs/pyglet/window/__init__.py index c3bb404..b0a13d9 100644 --- a/libs/pyglet/window/__init__.py +++ b/libs/pyglet/window/__init__.py @@ -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.""" diff --git a/libs/pyglet/window/win32/__init__.py b/libs/pyglet/window/win32/__init__.py index 2dfbd66..58f7292 100644 --- a/libs/pyglet/window/win32/__init__.py +++ b/libs/pyglet/window/win32/__init__.py @@ -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)