def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None: """ Run an WindowConfig entering a blocking main loop Args: config_cls: The WindowConfig class to render args: Override sys.args """ setup_basic_logging(config_cls.log_level) parser = create_parser() config_cls.add_arguments(parser) values = parse_args(args=args, parser=parser) config_cls.argv = values window_cls = get_local_window_cls(values.window) # Calculate window size size = values.size or config_cls.window_size size = int(size[0] * values.size_mult), int(size[1] * values.size_mult) # Resolve cursor show_cursor = values.cursor if show_cursor is None: show_cursor = config_cls.cursor window = window_cls( title=config_cls.title, size=size, fullscreen=config_cls.fullscreen or values.fullscreen, resizable=values.resizable if values.resizable is not None else config_cls.resizable, gl_version=config_cls.gl_version, aspect_ratio=config_cls.aspect_ratio, vsync=values.vsync if values.vsync is not None else config_cls.vsync, samples=values.samples if values.samples is not None else config_cls.samples, cursor=show_cursor if show_cursor is not None else True, ) window.print_context_info() activate_context(window=window) timer = Timer() window.config = config_cls(ctx=window.ctx, wnd=window, timer=timer) timer.start() while not window.is_closing: current_time, delta = timer.next_frame() if window.config.clear_color is not None: window.clear(*window.config.clear_color) else: window.use() window.render(current_time, delta) window.swap_buffers() _, duration = timer.stop() window.destroy() if duration > 0: logger.info("Duration: {0:.2f}s @ {1:.2f} FPS".format( duration, window.frames / duration))
def __init__( self, source: Union[moderngl.Texture, moderngl.Framebuffer] = None, framerate: Union[int, float] = 60, ): self._source = source self._framerate = framerate self._recording = False self._last_time: float = None self._filename: str = None self._width: int = None self._height: int = None self._timer = Timer() self._components: int = None # for textures if isinstance(self._source, moderngl.Texture): self._components = self._source.components
def __init__(self, scene, size=(1280, 720), **kwargs): super().__init__(size=size) digest_config(self, kwargs) self.scene = scene self.pressed_keys = set() self.title = str(scene) self.size = size print("window pixel ratio:", self.pixel_ratio) mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() # No idea why, but when self.position is set once # it sometimes doesn't actually change the position # to the specified tuple on the rhs, but doing it # twice seems to make it work. ¯\_(ツ)_/¯ initial_position = self.find_initial_position(size) self.position = initial_position self.position = initial_position
def __init__(self, renderer, size=config.window_size, **kwargs): monitors = get_monitors() mon_index = config.window_monitor monitor = monitors[min(mon_index, len(monitors) - 1)] if size == "default": # make window_width half the width of the monitor # but make it full screen if --fullscreen window_width = monitor.width if not config.fullscreen: window_width //= 2 # by default window_height = 9/16 * window_width window_height = int( window_width * config.frame_height // config.frame_width, ) size = (window_width, window_height) else: size = tuple(size) super().__init__(size=size) self.title = f"Manim Community {__version__}" self.size = size self.renderer = renderer mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() self.swap_buffers() initial_position = self.find_initial_position(size, monitor) self.position = initial_position
def test_clock_timer(self): """Quick and dirty scheduling test""" timer = Timer() timer.start() scheduler = Scheduler(timer) self.test_value = False scheduler.run_once(self.set_value, 0.1, arguments=(True, )) time.sleep(0.11) scheduler.execute() self.assertTrue(self.test_value) self.test_value = 0 event = scheduler.run_every(self.increase_value, 0.1) for _ in range(30): # simulate a render loop scheduler.execute() time.sleep(0.01) self.assertEqual(self.test_value, 3) scheduler.cancel(event) time.sleep(0.2) scheduler.execute() self.assertEqual(self.test_value, 3)
def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None: """ Run an WindowConfig entering a blocking main loop Args: config_cls: The WindowConfig class to render args: Override sys.args """ values = parse_args(args) window_cls = get_local_window_cls(values.window) window = window_cls( title=config_cls.title, size=config_cls.window_size, fullscreen=values.fullscreen, resizable=config_cls.resizable, gl_version=config_cls.gl_version, aspect_ratio=config_cls.aspect_ratio, vsync=values.vsync, samples=values.samples, cursor=values.cursor, ) window.print_context_info() window.config = config_cls(ctx=window.ctx, wnd=window) timer = Timer() timer.start() while not window.is_closing: current_time, delta = timer.next_frame() window.ctx.screen.use() window.render(current_time, delta) window.swap_buffers() _, duration = timer.stop() window.destroy() print("Duration: {0:.2f}s @ {1:.2f} FPS".format(duration, window.frames / duration))
def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None: """ Run an WindowConfig entering a blocking main loop Args: config_cls: The WindowConfig class to render Keyword Args: timer: A custom timer instance args: Override sys.args """ setup_basic_logging(config_cls.log_level) parser = create_parser() config_cls.add_arguments(parser) values = parse_args(args=args, parser=parser) config_cls.argv = values window_cls = get_local_window_cls(values.window) # Calculate window size size = values.size or config_cls.window_size size = int(size[0] * values.size_mult), int(size[1] * values.size_mult) # Resolve cursor show_cursor = values.cursor if show_cursor is None: show_cursor = config_cls.cursor window = window_cls( title=config_cls.title, size=size, fullscreen=config_cls.fullscreen or values.fullscreen, resizable=values.resizable if values.resizable is not None else config_cls.resizable, gl_version=config_cls.gl_version, aspect_ratio=config_cls.aspect_ratio, vsync=values.vsync if values.vsync is not None else config_cls.vsync, samples=values.samples if values.samples is not None else config_cls.samples, cursor=show_cursor if show_cursor is not None else True, ) window.print_context_info() activate_context(window=window) timer = timer or Timer() config = config_cls(ctx=window.ctx, wnd=window, timer=timer) # Avoid the event assigning in the property setter for now # We want the even assigning to happen in WindowConfig.__init__ # so users are free to assign them in their own __init__. window._config = weakref.ref(config) # Swap buffers once before staring the main loop. # This can trigged additional resize events reporting # a more accurate buffer size window.swap_buffers() window.set_default_viewport() timer.start() while not window.is_closing: current_time, delta = timer.next_frame() if config.clear_color is not None: window.clear(*config.clear_color) # Always bind the window framebuffer before calling render window.use() window.render(current_time, delta) if not window.is_closing: window.swap_buffers() _, duration = timer.stop() window.destroy() if duration > 0: logger.info("Duration: {0:.2f}s @ {1:.2f} FPS".format( duration, window.frames / duration))
class Window(PygletWindow): fullscreen = False resizable = True gl_version = (3, 3) vsync = True cursor = True def __init__(self, scene: Scene, size: tuple[int, int] = (1280, 720), **kwargs): super().__init__(size=size) digest_config(self, kwargs) self.scene = scene self.pressed_keys = set() self.title = str(scene) self.size = size mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() # No idea why, but when self.position is set once # it sometimes doesn't actually change the position # to the specified tuple on the rhs, but doing it # twice seems to make it work. ¯\_(ツ)_/¯ initial_position = self.find_initial_position(size) self.position = initial_position self.position = initial_position def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]: custom_position = get_customization()["window_position"] monitors = get_monitors() mon_index = get_customization()["window_monitor"] monitor = monitors[min(mon_index, len(monitors) - 1)] window_width, window_height = size # Position might be specified with a string of the form # x,y for integers x and y if "," in custom_position: return tuple(map(int, custom_position.split(","))) # Alternatively, it might be specified with a string like # UR, OO, DL, etc. specifying what corner it should go to char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2} width_diff = monitor.width - window_width height_diff = monitor.height - window_height return ( monitor.x + char_to_n[custom_position[1]] * width_diff // 2, -monitor.y + char_to_n[custom_position[0]] * height_diff // 2, ) # Delegate event handling to scene def pixel_coords_to_space_coords(self, px: int, py: int, relative: bool = False) -> np.ndarray: pw, ph = self.size fw, fh = self.scene.camera.get_frame_shape() fc = self.scene.camera.get_frame_center() if relative: return np.array([px / pw, py / ph, 0]) else: return np.array([ fc[0] + px * fw / pw - fw / 2, fc[1] + py * fh / ph - fh / 2, 0 ]) def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None: super().on_mouse_motion(x, y, dx, dy) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_motion(point, d_point) def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int) -> None: super().on_mouse_drag(x, y, dx, dy, buttons, modifiers) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_drag(point, d_point, buttons, modifiers) def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None: super().on_mouse_press(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_press(point, button, mods) def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None: super().on_mouse_release(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_release(point, button, mods) def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None: super().on_mouse_scroll(x, y, x_offset, y_offset) point = self.pixel_coords_to_space_coords(x, y) offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.scene.on_mouse_scroll(point, offset) def on_key_press(self, symbol: int, modifiers: int) -> None: self.pressed_keys.add(symbol) # Modifiers? super().on_key_press(symbol, modifiers) self.scene.on_key_press(symbol, modifiers) def on_key_release(self, symbol: int, modifiers: int) -> None: self.pressed_keys.difference_update({symbol}) # Modifiers? super().on_key_release(symbol, modifiers) self.scene.on_key_release(symbol, modifiers) def on_resize(self, width: int, height: int) -> None: super().on_resize(width, height) self.scene.on_resize(width, height) def on_show(self) -> None: super().on_show() self.scene.on_show() def on_hide(self) -> None: super().on_hide() self.scene.on_hide() def on_close(self) -> None: super().on_close() self.scene.on_close() def is_key_pressed(self, symbol: int) -> bool: return (symbol in self.pressed_keys)
class VideoCapture: """ ``VideoCapture`` it's an utility class to capture runtime render and save it as video. Example: .. code:: python import moderngl_window from moderngl_window.capture import VideoCapture class CaptureTest(modenrgl_window.WindowConfig): def __init__(self, **kwargs): super().__init__(**kwargs) # do other initialization # define VideoCapture class self.cap = VideoCapture() # start recording self.cap.start_capture( filename="video.mp4", target_fb = self.wnd.fbo ) def render(self, time, frametime): # do other render stuff # call record function after self.cap.dump() def close(self): # if realease func is not called during # runtime. make sure to do it on the closing event self.cap.release() """ def __init__(self): self._ffmpeg = None self._video_timer = Timer() self._filename: str = None self._target_fb: moderngl.Framebuffer = None self._framerate: int = None self._last_frame = None self._recording = False @property def framerate(self) -> int: return self._framerate @framerate.setter def framerate(self, value: int): self._framerate = value @property def target_fb(self) -> moderngl.Framebuffer: return self._target_fb @target_fb.setter def target_fb(self, value: moderngl.Framebuffer): self._target_fb = value @property def filename(self) -> str: return self._filename @filename.setter def filename(self, value: str): self._filename = value def dump(self): """ Read data from the target framebuffer and dump the raw data into ffmpeg stdin. Call this function at the end of `render` function Frame are saved respecting the video framerate. """ if not self._recording: return # in theory to capture a frame at certain speed i'm testing if # the time passed after the last frame is at least dt = 1./target_fps . # This prevent the higher framerate during runtime to exceed the # target framerate of the video. This doesn't work if runtime framerate # is lower than target framerate. if (self._video_timer.time - self._last_frame) >= 1./self._framerate: data = self._target_fb.read(components=3) self._ffmpeg.stdin.write(data) self._last_frame = self._video_timer.time def start(self, filename: str = None, target_fb: moderngl.Framebuffer = None, framerate=60): """ Start ffmpeg pipe subprocess. Call this at the end of __init__ function. Args: filename (str): name of the output file fb (moderngl.Framebuffer): target framebuffer to record framerate (int): framerate of the video """ if not target_fb: raise Exception("target framebuffer can't be: None") else: self._target_fb = target_fb self._framerate = framerate if not filename: now = datetime.datetmie.now() filename = f'video_{now:%Y-%m-%d_%H:%M:%S.%f}.mp4' self._filename = filename width = target_fb.width height = target_fb.height # took from Wasaby2D project command = [ 'ffmpeg', '-y', # (optional) overwrite output file if it exists '-f', 'rawvideo', '-vcodec', 'rawvideo', '-s', f'{width}x{height}', # size of one frame '-pix_fmt', 'rgb24', '-r', f'{framerate}', # frames per second '-i', '-', # The imput comes from a pipe '-vf', 'vflip', '-an', # Tells FFMPEG not to expect any audio filename, ] # ffmpeg binary need to be on the PATH. try: self._ffmpeg = subprocess.Popen( command, stdin=subprocess.PIPE, bufsize=0 ) except FileNotFoundError: print("ffmpeg command not found.") return self._video_timer.start() self._last_frame = self._video_timer.time self._recording = True print("Started video Recording") def release(self): """ Stop the recording process """ if self._recording: self._ffmpeg.stdin.close() ret = self._ffmpeg.wait() if ret == 0: print("Video saved succesfully") else: print("Error writing video.") self._recording = None self._video_timer.stop()
class Window(PygletWindow): fullscreen = False resizable = True gl_version = (3, 3) vsync = True cursor = True def __init__(self, renderer, size=None, **kwargs): if size is None: # Default to making window half the screen size # but make it full screen if --fullscreen is passed in monitors = get_monitors() mon_index = config.window_monitor monitor = monitors[min(mon_index, len(monitors) - 1)] window_width = monitor.width if not config.fullscreen: window_width //= 2 window_height = int(window_width * config.frame_height // config.frame_width) size = (window_width, window_height) super().__init__(size=size) self.title = f"Manim Community {__version__}" self.size = size self.renderer = renderer mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() self.swap_buffers() initial_position = self.find_initial_position(size, monitor) self.position = initial_position # Delegate event handling to scene. def on_mouse_motion(self, x, y, dx, dy): super().on_mouse_motion(x, y, dx, dy) point = self.renderer.pixel_coords_to_space_coords(x, y) d_point = self.renderer.pixel_coords_to_space_coords(dx, dy, relative=True) self.renderer.scene.on_mouse_motion(point, d_point) def on_mouse_scroll(self, x, y, x_offset: float, y_offset: float): super().on_mouse_scroll(x, y, x_offset, y_offset) point = self.renderer.pixel_coords_to_space_coords(x, y) offset = self.renderer.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.renderer.scene.on_mouse_scroll(point, offset) def on_key_press(self, symbol, modifiers): self.renderer.pressed_keys.add(symbol) super().on_key_press(symbol, modifiers) self.renderer.scene.on_key_press(symbol, modifiers) def on_key_release(self, symbol, modifiers): if symbol in self.renderer.pressed_keys: self.renderer.pressed_keys.remove(symbol) super().on_key_release(symbol, modifiers) self.renderer.scene.on_key_release(symbol, modifiers) def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): super().on_mouse_drag(x, y, dx, dy, buttons, modifiers) point = self.renderer.pixel_coords_to_space_coords(x, y) d_point = self.renderer.pixel_coords_to_space_coords(dx, dy, relative=True) self.renderer.scene.on_mouse_drag(point, d_point, buttons, modifiers) def find_initial_position(self, size, monitor): custom_position = config.window_position window_width, window_height = size # Position might be specified with a string of the form # x,y for integers x and y if len(custom_position) == 1: raise ValueError( "window_position must specify both Y and X positions (Y/X -> UR). Also accepts LEFT/RIGHT/ORIGIN/UP/DOWN." ) # in the form Y/X (UR) if custom_position == "LEFT" or custom_position == "RIGHT": custom_position = "O" + custom_position[0] elif custom_position == "UP" or custom_position == "DOWN": custom_position = custom_position[0] + "O" elif custom_position == "ORIGIN": custom_position = "O" * 2 elif "," in custom_position: return tuple(map(int, custom_position.split(","))) # Alternatively, it might be specified with a string like # UR, OO, DL, etc. specifying what corner it should go to char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2} width_diff = monitor.width - window_width height_diff = monitor.height - window_height return ( monitor.x + char_to_n[custom_position[1]] * width_diff // 2, -monitor.y + char_to_n[custom_position[0]] * height_diff // 2, )
class BaseVideoCapture: """ ``BaseVideoCapture`` is a base class to video capture Args: source (moderngl.Texture, moderngl.Framebuffer): the source of the capture framerate (int, float) : the framerate of the video, by thefault is 60 fps if the source is texture there are some requirements: - dtype = 'f1'; - components >= 3. """ def __init__( self, source: Union[moderngl.Texture, moderngl.Framebuffer] = None, framerate: Union[int, float] = 60, ): self._source = source self._framerate = framerate self._recording = False self._last_time: float = None self._filename: str = None self._width: int = None self._height: int = None self._timer = Timer() self._components: int = None # for textures if isinstance(self._source, moderngl.Texture): self._components = self._source.components def _dump_frame(self, frame): """ custom function called during self.save() Args: frame: frame data in bytes """ raise NotImplementedError("override this function") def _start_func(self) -> bool: """ custom function called during self.start_capture() must return a True if this function complete without errors """ raise NotImplementedError("override this function") def _release_func(self): """ custom function called during self.realease() """ raise NotImplementedError("override this function") def _get_wh(self): """ Return a tuple of the width and the height of the source """ return self._source.width, self._source.height def _remove_file(self): """ Remove the filename of the video is it exist """ if os.path.exists(self._filename): os.remove(self._filename) def start_capture(self, filename: str = None, framerate: Union[int, float] = 60): """ Start the capturing process Args: filename (str): name of the output file framerate (int, float): framerate of the video if filename is not specified it will be generated based on the datetime. """ if self._recording: print("Capturing is already started") return # ensure the texture has the correct dtype and components if isinstance(self._source, moderngl.Texture): if self._source.dtype != 'f1': print("source type: moderngl.Texture must be type `f1` ") return if self._components < 3: print( "source type: moderngl.Texture must have at least 3 components" ) return if not filename: now = datetime.datetime.now() filename = f'video_{now:%Y%m%d_%H%M%S}.mp4' self._filename = filename self._framerate = framerate self._width, self._height = self._get_wh() # if something goes wrong with the start # function, just stop and release the # capturing process if not self._start_func(): self.release() print("Capturing failed") return self._timer.start() self._last_time = self._timer.time self._recording = True def save(self): """ Save function to call at the end of render function """ if not self._recording: return dt = 1. / self._framerate if self._timer.time - self._last_time > dt: # start counting self._last_time = self._timer.time if isinstance(self._source, moderngl.Framebuffer): # get data from framebuffer frame = self._source.read(components=3) self._dump_frame(frame) else: # get data from texture frame = self._source.read() self._dump_frame(frame) def release(self): """ Stop the recording process """ if self._recording: self._release_func() self._timer.stop() print(f"Video file succesfully saved as {self._filename}") self._recording = None
class Window(PygletWindow): fullscreen = False resizable = True gl_version = (3, 3) vsync = True samples = 1 cursor = True def __init__(self, scene, **kwargs): super().__init__(**kwargs) digest_config(self, kwargs) self.scene = scene self.title = str(scene) self.pressed_keys = set() self.position = self.find_initial_position() mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() def find_initial_position(self): custom_position = get_customization()["window_position"] monitor = get_monitors()[0] window_width, window_height = self.size # Position might be specified with a string of the form # x,y for integers x and y if "," in custom_position: return tuple(map(int, custom_position.split(","))) # Alternatively, it might be specified with a string like # UR, OO, DL, etc. specifiying what corner it should go to char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2} width_diff = monitor.width - window_width height_diff = monitor.height - window_height return ( char_to_n[custom_position[1]] * width_diff // 2, char_to_n[custom_position[0]] * height_diff // 2, ) # Delegate event handling to scene def pixel_coords_to_space_coords(self, px, py, relative=False): return self.scene.camera.pixel_coords_to_space_coords(px, py, relative) def on_mouse_motion(self, x, y, dx, dy): super().on_mouse_motion(x, y, dx, dy) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_motion(point, d_point) def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): super().on_mouse_drag(x, y, dx, dy, buttons, modifiers) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_drag(point, d_point, buttons, modifiers) def on_mouse_press(self, x: int, y: int, button, mods): super().on_mouse_press(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_press(point, button, mods) def on_mouse_release(self, x: int, y: int, button, mods): super().on_mouse_release(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_release(point, button, mods) def on_mouse_scroll(self, x, y, x_offset: float, y_offset: float): super().on_mouse_scroll(x, y, x_offset, y_offset) point = self.pixel_coords_to_space_coords(x, y) offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.scene.on_mouse_scroll(point, offset) def on_key_press(self, symbol, modifiers): self.pressed_keys.add(symbol) # Modifiers? super().on_key_press(symbol, modifiers) self.scene.on_key_press(symbol, modifiers) def on_key_release(self, symbol, modifiers): self.pressed_keys.difference_update({symbol}) # Modifiers? super().on_key_release(symbol, modifiers) self.scene.on_key_release(symbol, modifiers) def on_resize(self, width: int, height: int): super().on_resize(width, height) self.scene.on_resize(width, height) def on_show(self): super().on_show() self.scene.on_show() def on_hide(self): super().on_hide() self.scene.on_hide() def on_close(self): super().on_close() self.scene.on_close() def is_key_pressed(self, symbol): return (symbol in self.pressed_keys)
class Window(PygletWindow): size = (DEFAULT_PIXEL_WIDTH, DEFAULT_PIXEL_HEIGHT) fullscreen = False resizable = True gl_version = (3, 3) vsync = True samples = 8 cursor = True def __init__(self, scene, **kwargs): digest_config(self, kwargs) super().__init__(**kwargs) self.scene = scene self.title = str(scene) # Put at the top of the screen self.position = (self.position[0], 0) mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() # Delegate event handling to scene def pixel_coords_to_space_coords(self, px, py, relative=False): return self.scene.camera.pixel_coords_to_space_coords(px, py, relative) def on_mouse_motion(self, x, y, dx, dy): super().on_mouse_motion(x, y, dx, dy) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_motion(point, d_point) def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): super().on_mouse_drag(x, y, dx, dy, buttons, modifiers) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_drag(point, d_point, buttons, modifiers) def on_mouse_press(self, x: int, y: int, button, mods): super().on_mouse_press(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_press(point, button, mods) def on_mouse_release(self, x: int, y: int, button, mods): super().on_mouse_release(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_release(point, button, mods) def on_mouse_scroll(self, x, y, x_offset: float, y_offset: float): super().on_mouse_scroll(x, y, x_offset, y_offset) point = self.pixel_coords_to_space_coords(x, y) offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.scene.on_mouse_scroll(point, offset) def on_key_release(self, symbol, modifiers): super().on_key_release(symbol, modifiers) self.scene.on_key_release(symbol, modifiers) def on_key_press(self, symbol, modifiers): super().on_key_press(symbol, modifiers) self.scene.on_key_press(symbol, modifiers) def on_resize(self, width: int, height: int): super().on_resize(width, height) self.scene.on_resize(width, height) def on_show(self): super().on_show() self.scene.on_show() def on_hide(self): super().on_hide() self.scene.on_hide() def on_close(self): super().on_close() self.scene.on_close()