window.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from __future__ import annotations
  2. import numpy as np
  3. import moderngl_window as mglw
  4. from moderngl_window.context.pyglet.window import Window as PygletWindow
  5. from moderngl_window.timers.clock import Timer
  6. from screeninfo import get_monitors
  7. from functools import wraps
  8. from manimlib.constants import FRAME_SHAPE
  9. from manimlib.utils.customization import get_customization
  10. from typing import TYPE_CHECKING
  11. if TYPE_CHECKING:
  12. from manimlib.scene.scene import Scene
  13. class Window(PygletWindow):
  14. fullscreen: bool = False
  15. resizable: bool = True
  16. gl_version: tuple[int, int] = (3, 3)
  17. vsync: bool = True
  18. cursor: bool = True
  19. def __init__(
  20. self,
  21. scene: Scene,
  22. size: tuple[int, int] = (1280, 720),
  23. samples: int = 0
  24. ):
  25. scene.window = self
  26. super().__init__(size=size, samples=samples)
  27. self.default_size = size
  28. self.default_position = self.find_initial_position(size)
  29. self.scene = scene
  30. self.pressed_keys = set()
  31. self.title = str(scene)
  32. self.size = size
  33. self._has_undrawn_event = True
  34. mglw.activate_context(window=self)
  35. self.timer = Timer()
  36. self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer)
  37. self.timer.start()
  38. self.to_default_position()
  39. def to_default_position(self):
  40. self.position = self.default_position
  41. # Hack. Sometimes, namely when configured to open in a separate window,
  42. # the window needs to be resized to display correctly.
  43. w, h = self.default_size
  44. self.size = (w - 1, h - 1)
  45. self.size = (w, h)
  46. def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
  47. custom_position = get_customization()["window_position"]
  48. monitors = get_monitors()
  49. mon_index = get_customization()["window_monitor"]
  50. monitor = monitors[min(mon_index, len(monitors) - 1)]
  51. window_width, window_height = size
  52. # Position might be specified with a string of the form
  53. # x,y for integers x and y
  54. if "," in custom_position:
  55. return tuple(map(int, custom_position.split(",")))
  56. # Alternatively, it might be specified with a string like
  57. # UR, OO, DL, etc. specifying what corner it should go to
  58. char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2}
  59. width_diff = monitor.width - window_width
  60. height_diff = monitor.height - window_height
  61. return (
  62. monitor.x + char_to_n[custom_position[1]] * width_diff // 2,
  63. -monitor.y + char_to_n[custom_position[0]] * height_diff // 2,
  64. )
  65. # Delegate event handling to scene
  66. def pixel_coords_to_space_coords(
  67. self,
  68. px: int,
  69. py: int,
  70. relative: bool = False
  71. ) -> np.ndarray:
  72. if not hasattr(self.scene, "frame"):
  73. return np.zeros(3)
  74. pixel_shape = np.array(self.size)
  75. fixed_frame_shape = np.array(FRAME_SHAPE)
  76. frame = self.scene.frame
  77. coords = np.zeros(3)
  78. coords[:2] = (fixed_frame_shape / pixel_shape) * np.array([px, py])
  79. if not relative:
  80. coords[:2] -= 0.5 * fixed_frame_shape
  81. return frame.from_fixed_frame_point(coords, relative)
  82. def has_undrawn_event(self) -> bool:
  83. return self._has_undrawn_event
  84. def swap_buffers(self):
  85. super().swap_buffers()
  86. self._has_undrawn_event = False
  87. @staticmethod
  88. def note_undrawn_event(func: Callable[..., T]) -> Callable[..., T]:
  89. @wraps(func)
  90. def wrapper(self, *args, **kwargs):
  91. func(self, *args, **kwargs)
  92. self._has_undrawn_event = True
  93. return wrapper
  94. @note_undrawn_event
  95. def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:
  96. super().on_mouse_motion(x, y, dx, dy)
  97. point = self.pixel_coords_to_space_coords(x, y)
  98. d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True)
  99. self.scene.on_mouse_motion(point, d_point)
  100. @note_undrawn_event
  101. def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int) -> None:
  102. super().on_mouse_drag(x, y, dx, dy, buttons, modifiers)
  103. point = self.pixel_coords_to_space_coords(x, y)
  104. d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True)
  105. self.scene.on_mouse_drag(point, d_point, buttons, modifiers)
  106. @note_undrawn_event
  107. def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None:
  108. super().on_mouse_press(x, y, button, mods)
  109. point = self.pixel_coords_to_space_coords(x, y)
  110. self.scene.on_mouse_press(point, button, mods)
  111. @note_undrawn_event
  112. def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None:
  113. super().on_mouse_release(x, y, button, mods)
  114. point = self.pixel_coords_to_space_coords(x, y)
  115. self.scene.on_mouse_release(point, button, mods)
  116. @note_undrawn_event
  117. def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None:
  118. super().on_mouse_scroll(x, y, x_offset, y_offset)
  119. point = self.pixel_coords_to_space_coords(x, y)
  120. offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True)
  121. self.scene.on_mouse_scroll(point, offset, x_offset, y_offset)
  122. @note_undrawn_event
  123. def on_key_press(self, symbol: int, modifiers: int) -> None:
  124. self.pressed_keys.add(symbol) # Modifiers?
  125. super().on_key_press(symbol, modifiers)
  126. self.scene.on_key_press(symbol, modifiers)
  127. @note_undrawn_event
  128. def on_key_release(self, symbol: int, modifiers: int) -> None:
  129. self.pressed_keys.difference_update({symbol}) # Modifiers?
  130. super().on_key_release(symbol, modifiers)
  131. self.scene.on_key_release(symbol, modifiers)
  132. @note_undrawn_event
  133. def on_resize(self, width: int, height: int) -> None:
  134. super().on_resize(width, height)
  135. self.scene.on_resize(width, height)
  136. @note_undrawn_event
  137. def on_show(self) -> None:
  138. super().on_show()
  139. self.scene.on_show()
  140. @note_undrawn_event
  141. def on_hide(self) -> None:
  142. super().on_hide()
  143. self.scene.on_hide()
  144. @note_undrawn_event
  145. def on_close(self) -> None:
  146. super().on_close()
  147. self.scene.on_close()
  148. def is_key_pressed(self, symbol: int) -> bool:
  149. return (symbol in self.pressed_keys)