-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cf23707
commit 322b56d
Showing
3 changed files
with
388 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,382 @@ | ||
# Copyright (C) 2023 ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos | ||
|
||
from typing import Tuple | ||
from PyQt6 import QtGui, QtWidgets, QtCore, QtOpenGLWidgets | ||
from PyQt6.QtOpenGL import QOpenGLWindow, QOpenGLVersionProfile | ||
|
||
from moderngl_window.context.base import BaseWindow | ||
from moderngl_window.context.pyqt5.keys import Keys | ||
|
||
# class GLWindow(QOpenGLWindow): | ||
# def __init__(self, gl_version, vsync): | ||
# super().__init__() | ||
# self.gl_version = gl_version | ||
# self.vsync = vsync | ||
|
||
# def initializeGL(self): | ||
# self.fmt = QtGui.QSurfaceFormat() | ||
# self.fmt.setVersion(self.gl_version[0], self.gl_version[1]) | ||
# self.fmt.setProfile(QtGui.QSurfaceFormat.OpenGLContextProfile.CoreProfile) | ||
# self.fmt.setDepthBufferSize(24) | ||
# self.fmt.setSwapBehavior(QtGui.QSurfaceFormat.SwapBehavior.DoubleBuffer) | ||
# self.fmt.setSwapInterval(1 if self.vsync else 0) | ||
# self.fmt.setAlphaBufferSize(8) | ||
|
||
# def paintGL(self) -> None: | ||
# pass | ||
|
||
class PyQt6Window(BaseWindow): | ||
""" | ||
A basic window implementation using PyQt5 with the goal of | ||
creating an OpenGL context and handle keyboard and mouse input. | ||
This window bypasses Qt's own event loop to make things as flexible as possible. | ||
If you need to use the event loop and are using other features | ||
in Qt as well, this example can still be useful as a reference | ||
when creating your own window. | ||
""" | ||
|
||
#: Name of the window | ||
name = "pyqt6" | ||
#: PyQt5 specific key constants | ||
keys = Keys | ||
|
||
# PyQt supports mode buttons, but we are limited by other libraries | ||
_mouse_button_map = { | ||
QtCore.Qt.MouseButton.LeftButton: 1, | ||
QtCore.Qt.MouseButton.RightButton: 2, | ||
QtCore.Qt.MouseButton.MiddleButton: 3, | ||
} | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
""" | ||
# Specify OpenGL context parameters | ||
gl = QSurfaceFormat() | ||
gl.setVersion(self.gl_version[0], self.gl_version[1]) | ||
# Configure multisampling if needed | ||
if self.samples > 1: | ||
# gl.setSampleBuffers(True) | ||
gl.setSamples(int(self.samples)) | ||
# We need an application object, but we are bypassing the library's | ||
# internal event loop to avoid unnecessary work | ||
# Create the OpenGL widget | ||
self._widget = QtOpenGLWidgets.QOpenGLWidget() | ||
self._widget.setFormat(gl) | ||
""" | ||
self._app = QtWidgets.QApplication([]) | ||
# self._widget = GLWindow(self.gl_version, self._vsync) | ||
self._widget = QtOpenGLWidgets.QOpenGLWidget() | ||
|
||
fmt = QtGui.QSurfaceFormat() | ||
fmt.setVersion(self.gl_version[0], self.gl_version[1]) | ||
fmt.setProfile(QtGui.QSurfaceFormat.OpenGLContextProfile.CoreProfile) | ||
fmt.setDepthBufferSize(24) | ||
fmt.setSwapBehavior(QtGui.QSurfaceFormat.SwapBehavior.DoubleBuffer) | ||
fmt.setSwapInterval(1 if self.vsync else 0) | ||
fmt.setAlphaBufferSize(8) | ||
# self._widget.setFormat(fmt) | ||
|
||
self.title = self._title | ||
|
||
# If fullscreen we change the window to match the desktop on the primary screen | ||
if self.fullscreen: | ||
rect = QtWidgets.QDesktopWidget().screenGeometry() | ||
self._width = rect.width() | ||
self._height = rect.height() | ||
self._buffer_width = rect.width() * self._widget.devicePixelRatio() | ||
self._buffer_height = rect.height() * self._widget.devicePixelRatio() | ||
|
||
if self.resizable: | ||
# # Ensure a valid resize policy when window is resizable | ||
# size_policy = QtWidgets.QSizePolicy( | ||
# QtWidgets.QSizePolicy.Policy.Expanding, | ||
# QtWidgets.QSizePolicy.Policy.Expanding, | ||
# ) | ||
# self._widget.setSizePolicy(size_policy) | ||
self._widget.resize(self.width, self.height) | ||
# else: | ||
# self._widget.setFixedSize(self.width, self.height) | ||
|
||
# Center the window on the screen if in window mode | ||
if not self.fullscreen: | ||
# center_window_position = ( | ||
# # HACK: Here we changed division to integer division, | ||
# # because move requires its arguments to be integers. | ||
# self.position[0] - self.width // 2, | ||
# self.position[1] - self.height // 2, | ||
# ) | ||
# self._widget.move(*center_window_position) | ||
pass | ||
|
||
# Needs to be set before show() | ||
self._widget.resizeGL = self.resize | ||
|
||
self.cursor = self._cursor | ||
|
||
if self.fullscreen: | ||
self._widget.showFullScreen() | ||
else: | ||
self._widget.show() | ||
|
||
# We want mouse position events | ||
# self._widget.setMouseTracking(True) | ||
|
||
# Override event functions in qt | ||
self._widget.keyPressEvent = self.key_pressed_event | ||
self._widget.keyReleaseEvent = self.key_release_event | ||
self._widget.mouseMoveEvent = self.mouse_move_event | ||
self._widget.mousePressEvent = self.mouse_press_event | ||
self._widget.mouseReleaseEvent = self.mouse_release_event | ||
self._widget.wheelEvent = self.mouse_wheel_event | ||
self._widget.closeEvent = self.close_event | ||
self._widget.showEvent = self.show_event | ||
self._widget.hideEvent = self.hide_event | ||
|
||
# # Attach to the context | ||
self.init_mgl_context() | ||
|
||
# Ensure retina and 4k displays get the right viewport | ||
self._buffer_width = int(self._width * self._widget.devicePixelRatio()) | ||
self._buffer_height = int(self._height * self._widget.devicePixelRatio()) | ||
|
||
self.set_default_viewport() | ||
|
||
def _set_icon(self, icon_path: str) -> None: | ||
self._widget.setWindowIcon(QtGui.QIcon(str(icon_path))) | ||
pass | ||
|
||
def _set_fullscreen(self, value: bool) -> None: | ||
if value: | ||
self._widget.showFullScreen() | ||
else: | ||
self._widget.showNormal() | ||
|
||
@property | ||
def size(self) -> Tuple[int, int]: | ||
"""Tuple[int, int]: current window size. | ||
This property also support assignment:: | ||
# Resize the window to 1000 x 1000 | ||
window.size = 1000, 1000 | ||
""" | ||
return self._width, self._height | ||
|
||
@size.setter | ||
def size(self, value: Tuple[int, int]): | ||
pos = self.position | ||
self._widget.setGeometry(pos[0], pos[1], value[0], value[1]) | ||
|
||
@property | ||
def position(self) -> Tuple[int, int]: | ||
"""Tuple[int, int]: The current window position. | ||
This property can also be set to move the window:: | ||
# Move window to 100, 100 | ||
window.position = 100, 100 | ||
""" | ||
geo = self._widget.geometry() | ||
return geo.x(), geo.y() | ||
|
||
@position.setter | ||
def position(self, value: Tuple[int, int]): | ||
self._widget.setGeometry(value[0], value[1], self._width, self._height) | ||
|
||
def swap_buffers(self) -> None: | ||
"""Swap buffers, set viewport, trigger events and increment frame counter""" | ||
self._widget.paintGL() | ||
self.set_default_viewport() | ||
QtWidgets.QApplication.processEvents() | ||
self._frames += 1 | ||
|
||
@property | ||
def cursor(self) -> bool: | ||
"""bool: Should the mouse cursor be visible inside the window? | ||
This property can also be assigned to:: | ||
# Disable cursor | ||
window.cursor = False | ||
""" | ||
return self._cursor | ||
|
||
@cursor.setter | ||
def cursor(self, value: bool): | ||
if value is True: | ||
self._widget.setCursor(QtCore.Qt.CursorShape.ArrowCursor) | ||
else: | ||
self._widget.setCursor(QtCore.Qt.CursorShape.BlankCursor) | ||
|
||
self._cursor = value | ||
|
||
@property | ||
def title(self) -> str: | ||
"""str: Window title. | ||
This property can also be set:: | ||
window.title = "New Title" | ||
""" | ||
return self._title | ||
|
||
@title.setter | ||
def title(self, value: str): | ||
self._widget.setWindowTitle(value) | ||
self._title = value | ||
|
||
def resize(self, width: int, height: int) -> None: | ||
"""Replacement for Qt's ``resizeGL`` method. | ||
Args: | ||
width: New window width | ||
height: New window height | ||
""" | ||
self._width = width // self._widget.devicePixelRatio() | ||
self._height = height // self._widget.devicePixelRatio() | ||
self._buffer_width = width | ||
self._buffer_height = height | ||
|
||
if self._ctx: | ||
self.set_default_viewport() | ||
|
||
# Make sure we notify the example about the resize | ||
super().resize(self._buffer_width, self._buffer_height) | ||
|
||
def _handle_modifiers(self, mods) -> None: | ||
"""Update modifiers""" | ||
self._modifiers.shift = bool(mods & QtCore.Qt.KeyboardModifier.ShiftModifier) | ||
self._modifiers.ctrl = bool(mods & QtCore.Qt.KeyboardModifier.ControlModifier) | ||
self._modifiers.alt = bool(mods & QtCore.Qt.KeyboardModifier.AltModifier) | ||
|
||
def _set_icon(self, icon_path: str) -> None: | ||
self._widget.setIcon(QtGui.QIcon(icon_path)) | ||
|
||
def key_pressed_event(self, event: QtGui.QKeyEvent) -> None: | ||
"""Process Qt key press events forwarding them to standard methods | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
if self._exit_key is not None and event.key() == self._exit_key: | ||
self.close() | ||
|
||
if self._fs_key is not None and event.key() == self._fs_key: | ||
self.fullscreen = not self.fullscreen | ||
|
||
self._handle_modifiers(event.modifiers()) | ||
self._key_pressed_map[event.key()] = True | ||
self._key_event_func(event.key(), self.keys.ACTION_PRESS, self._modifiers) | ||
|
||
text = event.text() | ||
if text.strip() or event.key() == self.keys.SPACE: | ||
self._unicode_char_entered_func(text) | ||
|
||
def key_release_event(self, event: QtGui.QKeyEvent) -> None: | ||
"""Process Qt key release events forwarding them to standard methods | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
self._handle_modifiers(event.modifiers()) | ||
self._key_pressed_map[event.key()] = False | ||
self._key_event_func(event.key(), self.keys.ACTION_RELEASE, self._modifiers) | ||
|
||
def mouse_move_event(self, event: QtGui.QMouseEvent) -> None: | ||
"""Forward mouse cursor position events to standard methods | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
x, y = event.pos().x(), event.pos().y() | ||
dx, dy = self._calc_mouse_delta(x, y) | ||
|
||
if self.mouse_states.any: | ||
self._mouse_drag_event_func(x, y, dx, dy) | ||
else: | ||
self._mouse_position_event_func(x, y, dx, dy) | ||
|
||
def mouse_press_event(self, event: QtGui.QMouseEvent) -> None: | ||
"""Forward mouse press events to standard methods | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
self._handle_modifiers(event.modifiers()) | ||
button = self._mouse_button_map.get(event.button()) | ||
if button is None: | ||
return | ||
print(button) | ||
self._handle_mouse_button_state_change(button, True) | ||
self._mouse_press_event_func(event.pos().x(), event.pos().y(), button) | ||
|
||
def mouse_release_event(self, event: QtGui.QMouseEvent) -> None: | ||
"""Forward mouse release events to standard methods | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
self._handle_modifiers(event.modifiers()) | ||
button = self._mouse_button_map.get(event.button()) | ||
if button is None: | ||
return | ||
print(button) | ||
|
||
self._handle_mouse_button_state_change(button, False) | ||
self._mouse_release_event_func(event.pos().x(), event.pos().y(), button) | ||
|
||
def mouse_wheel_event(self, event): | ||
"""Forward mouse wheel events to standard metods. | ||
From Qt docs: | ||
Returns the distance that the wheel is rotated, in eighths of a degree. | ||
A positive value indicates that the wheel was rotated forwards away from the user; | ||
a negative value indicates that the wheel was rotated backwards toward the user. | ||
Most mouse types work in steps of 15 degrees, in which case the delta value is a | ||
multiple of 120; i.e., 120 units * 1/8 = 15 degrees. | ||
However, some mice have finer-resolution wheels and send delta values that are less | ||
than 120 units (less than 15 degrees). To support this possibility, you can either | ||
cumulatively add the delta values from events until the value of 120 is reached, | ||
then scroll the widget, or you can partially scroll the widget in response to each | ||
wheel event. | ||
Args: | ||
event (QWheelEvent): Mouse wheel event | ||
""" | ||
self._handle_modifiers(event.modifiers()) | ||
point = event.angleDelta() | ||
self._mouse_scroll_event_func(point.x() / 120.0, point.y() / 120.0) | ||
|
||
def close_event(self, event) -> None: | ||
"""The standard PyQt close events | ||
Args: | ||
event: The qtevent instance | ||
""" | ||
self.close() | ||
|
||
def close(self): | ||
"""Close the window""" | ||
super().close() | ||
self._close_func() | ||
|
||
def show_event(self, event): | ||
"""The standard Qt show event""" | ||
self._iconify_func(False) | ||
|
||
def hide_event(self, event): | ||
"""The standard Qt hide event""" | ||
self._iconify_func(True) | ||
|
||
def destroy(self) -> None: | ||
"""Quit the Qt application to exit the window gracefully""" | ||
QtCore.QCoreApplication.instance().quit() |
Oops, something went wrong.