from typing import Tuple
import glfw
from moderngl_window.context.base import BaseWindow
from moderngl_window.context.glfw.keys import Keys
class Window(BaseWindow):
"""
Window based on GLFW
"""
#: Name of the window
name = 'glfw'
#: GLFW specific key constants
keys = Keys
# GLFW do support other buttons, but we are limited by other libraries
_mouse_button_map = {
0: 1,
1: 2,
2: 3,
}
[docs] def __init__(self, **kwargs):
super().__init__(**kwargs)
if not glfw.init():
raise ValueError("Failed to initialize glfw")
# Configure the OpenGL context
glfw.window_hint(glfw.CONTEXT_CREATION_API, glfw.NATIVE_CONTEXT_API)
glfw.window_hint(glfw.CLIENT_API, glfw.OPENGL_API)
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, self.gl_version[0])
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, self.gl_version[1])
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.RESIZABLE, self.resizable)
glfw.window_hint(glfw.DOUBLEBUFFER, True)
glfw.window_hint(glfw.DEPTH_BITS, 24)
glfw.window_hint(glfw.SAMPLES, self.samples)
monitor = None
if self.fullscreen:
monitor = glfw.get_primary_monitor()
mode = glfw.get_video_mode(monitor)
self._width, self._height = mode.size.width, mode.size.height
glfw.window_hint(glfw.RED_BITS, mode.bits.red)
glfw.window_hint(glfw.GREEN_BITS, mode.bits.green)
glfw.window_hint(glfw.BLUE_BITS, mode.bits.blue)
glfw.window_hint(glfw.REFRESH_RATE, mode.refresh_rate)
self._window = glfw.create_window(self.width, self.height, self.title, monitor, None)
self._has_focus = True
if not self._window:
glfw.terminate()
raise ValueError("Failed to create window")
self.cursor = self._cursor
self._buffer_width, self._buffer_height = glfw.get_framebuffer_size(self._window)
glfw.make_context_current(self._window)
if self.vsync:
glfw.swap_interval(1)
else:
glfw.swap_interval(0)
glfw.set_key_callback(self._window, self.glfw_key_event_callback)
glfw.set_cursor_pos_callback(self._window, self.glfw_mouse_event_callback)
glfw.set_mouse_button_callback(self._window, self.glfw_mouse_button_callback)
glfw.set_scroll_callback(self._window, self.glfw_mouse_scroll_callback)
glfw.set_window_size_callback(self._window, self.glfw_window_resize_callback)
glfw.set_char_callback(self._window, self.glfw_char_callback)
glfw.set_window_focus_callback(self._window, self.glfw_window_focus)
glfw.set_cursor_enter_callback(self._window, self.glfw_cursor_enter)
glfw.set_window_iconify_callback(self._window, self.glfw_window_iconify)
self.init_mgl_context()
self.set_default_viewport()
@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]):
glfw.set_window_size(self._window, 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
"""
return glfw.get_window_pos(self._window)
@position.setter
def position(self, value: Tuple[int, int]):
self._position = glfw.set_window_pos(self._window, value[0], value[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 not self.mouse_exclusivity:
if value is True:
glfw.set_input_mode(self._window, glfw.CURSOR, glfw.CURSOR_NORMAL)
elif value is False:
glfw.set_input_mode(self._window, glfw.CURSOR, glfw.CURSOR_HIDDEN)
self._cursor = value
@property
def mouse_exclusivity(self) -> bool:
"""bool: If mouse exclusivity is enabled.
When you enable mouse-exclusive mode, the mouse cursor is no longer
available. It is not merely hidden – no amount of mouse movement
will make it leave your application. This is for example useful
when you don't want the mouse leaving the screen when rotating
a 3d scene.
This property can also be set::
window.mouse_exclusivity = True
"""
return self._mouse_exclusivity
@mouse_exclusivity.setter
def mouse_exclusivity(self, value: bool):
self._mouse_exclusivity = value
if value is True:
self._mouse_pos = glfw.get_cursor_pos(self._window)
glfw.set_input_mode(self._window, glfw.CURSOR, glfw.CURSOR_DISABLED)
else:
self.cursor = self._cursor
@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):
glfw.set_window_title(self._window, value)
self._title = value
[docs] def close(self) -> None:
"""Suggest to glfw the window should be closed soon"""
self._close_func()
glfw.set_window_should_close(self._window, True)
@property
def is_closing(self):
"""bool: Checks if the window is scheduled for closing"""
return glfw.window_should_close(self._window)
[docs] def swap_buffers(self):
"""Swap buffers, increment frame counter and pull events"""
glfw.swap_buffers(self._window)
self._frames += 1
glfw.poll_events()
def _handle_modifiers(self, mods):
"""Checks key modifiers"""
self._modifiers.shift = mods & 1 == 1
self._modifiers.ctrl = mods & 2 == 2
self._modifiers.alt = mods & 4 == 4
[docs] def glfw_key_event_callback(self, window, key, scancode, action, mods):
"""Key event callback for glfw.
Translates and forwards keyboard event to :py:func:`keyboard_event`
Args:
window: Window event origin
key: The key that was pressed or released.
scancode: The system-specific scancode of the key.
action: ``GLFW_PRESS``, ``GLFW_RELEASE`` or ``GLFW_REPEAT``
mods: Bit field describing which modifier keys were held down.
"""
if self.exit_key is not None and key == self._exit_key:
self.close()
self._handle_modifiers(mods)
if action == self.keys.ACTION_PRESS:
self._key_pressed_map[key] = True
elif action == self.keys.ACTION_RELEASE:
self._key_pressed_map[key] = False
self._key_event_func(key, action, self._modifiers)
[docs] def glfw_mouse_event_callback(self, window, xpos, ypos):
"""Mouse position event callback from glfw.
Translates the events forwarding them to :py:func:`cursor_event`.
Screen coordinates relative to the top-left corner
Args:
window: The window
xpos: viewport x pos
ypos: viewport y pos
"""
xpos, ypos = int(xpos), int(ypos)
dx, dy = self._calc_mouse_delta(xpos, ypos)
if self.mouse_states.any:
self._mouse_drag_event_func(xpos, ypos, dx, dy)
else:
self._mouse_position_event_func(xpos, ypos, dx, dy)
[docs] def glfw_char_callback(self, window, codepoint: int):
"""Handle text input (only unicode charaters)
Args:
window: The glfw window
codepoint (int): The unicode codepoint
"""
self._unicode_char_entered_func(chr(codepoint))
[docs] def glfw_window_resize_callback(self, window, width, height):
"""
Window resize callback for glfw
Args:
window: The window
width: New width
height: New height
"""
self._width, self._height = width, height
self._buffer_width, self._buffer_height = glfw.get_framebuffer_size(self._window)
self.set_default_viewport()
super().resize(self._buffer_width, self._buffer_height)
[docs] def glfw_window_focus(self, window, focused: int):
"""Called when the window focus is changed.
Args:
window: The window instance
focused (int): 0: de-focus, 1: focused
"""
self._has_focus = True if focused == 1 else False
[docs] def glfw_cursor_enter(self, window, enter: int):
"""called when the cursor enters or leaves the content area of the window.
Args:
window: the window instance
enter (int): 0: leave, 1: enter
"""
pass
[docs] def glfw_window_iconify(self, window, iconified: int):
"""Called when the window is minimized or restored.
Args:
window: The window
iconified (int): 1 = minimized, 0 = restored.
"""
self._iconify_func(True if iconified == 1 else False)
[docs] def destroy(self):
"""Gracefully terminate GLFW"""
glfw.terminate()