Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion manim/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ def init_points(self) -> None:
self.set_height(self.initial_frame_shape[1], stretch=True)
self.move_to(self.center_point)

def reset(self) -> None:
"""restores camera to default orientation and position."""
self.set_euler_angles(theta=-TAU / 4, phi=0.0, gamma=0.0)
self.init_points()

def interpolate(
self,
mobject1: Mobject,
Expand Down Expand Up @@ -248,7 +253,7 @@ def increment_phi(self, dphi: float) -> Self:
:class:`Camera`
The camera after incrementing its phi angle.
"""
return self.set_phi(self._phi + dgamma)
return self.set_phi(self._phi + dphi)

def get_gamma(self) -> float:
"""Get the angle gamma by which the camera is rotated while standing on
Expand Down
2 changes: 1 addition & 1 deletion manim/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def create_window(self) -> WindowProtocol | None:
-------
A window if previewing, else None
"""
return Window() if config.preview else None
return Window(self.scene) if config.preview else None

def create_file_writer(self) -> FileWriterProtocol:
"""Create and return a file writer instance.
Expand Down
1 change: 1 addition & 0 deletions manim/mobject/opengl/opengl_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from manim.event_handler import EVENT_DISPATCHER
from manim.event_handler.event_listener import EventListener
from manim.event_handler.event_type import EventType
from manim.typing import Point3D
from manim.utils.bezier import integer_interpolate, interpolate
from manim.utils.color import *
from manim.utils.exceptions import MultiAnimationOverrideException
Expand Down
129 changes: 128 additions & 1 deletion manim/renderer/opengl_renderer_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from typing import TYPE_CHECKING, TypeVar

import moderngl_window as mglw
import numpy as np
from moderngl_window.context.pyglet.window import Window as PygletWindow
from moderngl_window.timers.clock import Timer
from screeninfo import get_monitors

from manim import __version__, config
from manim.event_handler.window import WindowProtocol
from manim.scene.scene import Scene

if TYPE_CHECKING:
from typing import TypeGuard
Expand All @@ -29,7 +31,9 @@ class Window(PygletWindow, WindowProtocol):
vsync: bool = True
cursor: bool = True

def __init__(self, window_size: str | tuple[int, ...] = config.window_size):
def __init__(
self, scene: Scene, window_size: str | tuple[int, ...] = config.window_size
):
# TODO: remove size argument from window init,
# move size computation below to config

Expand Down Expand Up @@ -65,6 +69,7 @@ def __init__(self, window_size: str | tuple[int, ...] = config.window_size):
raise ValueError(invalid_window_size_error_message)

super().__init__(size=size)
self.scene = scene
self.pressed_keys: set = set()
self.title = f"Manim Community {__version__}"
self.size = size
Expand Down Expand Up @@ -109,6 +114,128 @@ def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
-monitor.y + char_to_n[custom_position[0]] * height_diff // 2,
)

def on_key_press(self, symbol: int, modifiers: int) -> bool:
"""tie key pressing events to the scene response

Parameters
----------
symbol
the key that is pressed
modifiers
keys like shift or ctrl

Returns
-------
bool
whether pyglet handled the event or not

"""
# TODO: do we override pyglet's functions or call them like in this function?
self.scene.on_key_press(symbol, modifiers)
return super().on_key_press(symbol, modifiers)

def on_key_release(self, symbol: int, modifiers: int) -> None:
"""tie key release events to the scene response

Parameters
----------
symbol
the key that is released
modifiers
keys like shift or ctrl
"""
self.scene.on_key_release(symbol, modifiers)

def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:
"""tie mouse motion events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
dx
change of x pixel coordinates
dy
change of y pixel coordinates
"""
self.scene.on_mouse_motion(np.array([x, y, 0]), np.array([dx, dy, 0]))

def on_mouse_drag(
self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int
) -> None:
"""tie mouse drag events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
dx
change of x pixel coordinates
dy
change of y pixel coordinates
buttons
the mouse buttons currently pressed
modifiers
keys like shift or ctrl
"""
self.scene.on_mouse_drag(
np.array([x, y, 0]), np.array([dx, dy, 0]), buttons, modifiers
)

def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None:
"""tie mouse press events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
button
the mouse button that is pressed
mods
keys like shift or ctrl
"""
self.scene.on_mouse_press(np.array([x, y, 0]), button, mods)

def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None:
"""tie mouse release events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
button
the mouse button that is released
mods
keys like shift or ctrl
"""
self.scene.on_mouse_release(np.array([x, y, 0]), button, mods)

def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None:
"""tie mouse scrolling events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
x_offset
number of horizontal wheel ticks (not useful for most mice)
y_offset
number of vertical wheel ticks
"""
self.scene.on_mouse_scroll(
np.array([x, y, 0]), np.array([x_offset, y_offset, 0])
)


def tuple_len_2(pos: tuple[T, ...]) -> TypeGuard[tuple[T, T]]:
return len(pos) == 2
66 changes: 48 additions & 18 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,9 @@ def redo(self):
# Event handling

def on_mouse_motion(self, point: Point3D, d_point: Vector3D) -> None:
point = self._pos_window_to_camera(point)
d_point = self._d_pos_window_to_camera(d_point)

self.mouse_point.move_to(point)

event_data = {"point": point, "d_point": d_point}
Expand All @@ -534,25 +537,25 @@ def on_mouse_motion(self, point: Point3D, d_point: Vector3D) -> None:
if propagate_event is not None and propagate_event is False:
return

# TODO
return
frame = self.camera.frame
# Handle perspective changes
if self.window.is_key_pressed(ord(PAN_3D_KEY)):
frame.increment_theta(-self.pan_sensitivity * d_point[0])
frame.increment_phi(self.pan_sensitivity * d_point[1])
if EVENT_DISPATCHER.is_key_pressed(ord(PAN_3D_KEY)):
self.camera.increment_theta(-self.pan_sensitivity * d_point[0])
self.camera.increment_phi(self.pan_sensitivity * d_point[1])
# Handle frame movements
elif self.window.is_key_pressed(ord(FRAME_SHIFT_KEY)):
elif EVENT_DISPATCHER.is_key_pressed(ord(FRAME_SHIFT_KEY)):
shift = -d_point
shift[0] *= frame.get_width() / 2
shift[1] *= frame.get_height() / 2
transform = frame.get_inverse_camera_rotation_matrix()
shift[0] *= self.camera.get_width() / 2
shift[1] *= self.camera.get_height() / 2
transform = self.camera.get_inverse_rotation_matrix()
shift = np.dot(np.transpose(transform), shift)
frame.shift(shift)
self.camera.shift(shift)

def on_mouse_drag(
self, point: Point3D, d_point: Vector3D, buttons: int, modifiers: int
) -> None:
point = self._pos_window_to_camera(point)
d_point = self._d_pos_window_to_camera(d_point)

self.mouse_drag_point.move_to(point)

event_data = {
Expand All @@ -568,6 +571,8 @@ def on_mouse_drag(
return

def on_mouse_press(self, point: Point3D, button: int, mods: int) -> None:
point = self._pos_window_to_camera(point)

self.mouse_drag_point.move_to(point)
event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(
Expand All @@ -577,6 +582,8 @@ def on_mouse_press(self, point: Point3D, button: int, mods: int) -> None:
return

def on_mouse_release(self, point: Point3D, button: int, mods: int) -> None:
point = self._pos_window_to_camera(point)

event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(
EventType.MouseReleaseEvent, **event_data
Expand All @@ -585,21 +592,22 @@ def on_mouse_release(self, point: Point3D, button: int, mods: int) -> None:
return

def on_mouse_scroll(self, point: Point3D, offset: Vector3D) -> None:
point = self._pos_window_to_camera(point)

event_data = {"point": point, "offset": offset}
propagate_event = EVENT_DISPATCHER.dispatch(
EventType.MouseScrollEvent, **event_data
)
if propagate_event is not None and propagate_event is False:
return

frame = self.camera.frame
if self.window.is_key_pressed(ord(ZOOM_KEY)):
factor = 1 + np.arctan(10 * offset[1])
frame.scale(1 / factor, about_point=point)
if EVENT_DISPATCHER.is_key_pressed(ord(ZOOM_KEY)):
factor = 1 / 1.25 if offset[1] > 0 else 1.25
self.camera.scale(factor, about_point=point)
else:
transform = frame.get_inverse_camera_rotation_matrix()
transform = self.camera.get_inverse_rotation_matrix()
shift = np.dot(np.transpose(transform), offset)
frame.shift(-20.0 * shift)
self.camera.shift(-shift / 2)

def on_key_release(self, symbol: int, modifiers: int) -> None:
event_data = {"symbol": symbol, "modifiers": modifiers}
Expand All @@ -624,7 +632,7 @@ def on_key_press(self, symbol: int, modifiers: int) -> None:
return

if char == RESET_FRAME_KEY:
self.play(self.camera.frame.animate.to_default_state())
self.camera.reset()
elif char == "z" and modifiers == key.MOD_COMMAND:
self.undo()
elif char == "z" and modifiers == key.MOD_COMMAND | key.MOD_SHIFT:
Expand All @@ -648,6 +656,28 @@ def on_hide(self) -> None:
def on_close(self) -> None:
pass

def _pos_window_to_camera(self, point: Point3D) -> Point3D:
"""The window gives position coordinates in pixels, we need them in camera coordinates for intuitive interactions."""
return np.array(
[
point[0] / self.manager.window.size[0] * self.camera.get_width()
- self.camera.get_width() / 2,
point[1] / self.manager.window.size[1] * self.camera.get_height()
- self.camera.get_height() / 2,
0,
]
)

def _d_pos_window_to_camera(self, d_point: Point3D) -> Point3D:
"""The window gives positions differentials in pixels, we need them in camera units for intuitive interactions."""
return np.array(
[
d_point[0] / self.manager.window.size[0] * self.camera.get_width(),
d_point[1] / self.manager.window.size[1] * self.camera.get_height(),
0,
]
)


class SceneState:
def __init__(self, scene: Scene, ignore: Iterable[Mobject] | None = None) -> None:
Expand Down