From d6b23324a699b51e4374f3b15fe1dfb99ae9f9b8 Mon Sep 17 00:00:00 2001 From: Sahil Makhijani Date: Sun, 31 Jan 2021 16:05:55 +0530 Subject: [PATCH] Interactive Mobjects Performance Improvements --- manimlib/mobject/interactive.py | 77 +++++++++++++++++---------------- manimlib/scene/scene.py | 29 +++++++++---- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/manimlib/mobject/interactive.py b/manimlib/mobject/interactive.py index 7e72d816..79c80f23 100644 --- a/manimlib/mobject/interactive.py +++ b/manimlib/mobject/interactive.py @@ -22,21 +22,19 @@ class MotionMobject(Mobject): You could hold and drag this object to any position """ - CONFIG = { - "listen_to_events": True - } - def __init__(self, mobject, **kwargs): super().__init__(**kwargs) + assert(isinstance(mobject, Mobject)) self.mobject = mobject + self.mobject.listen_to_events = True + self.mobject.on_mouse_drag = self.mob_on_mouse_drag # To avoid locking it as static mobject self.mobject.add_updater(lambda mob: None) self.add(mobject) - def on_mouse_drag(self, point, d_point, buttons, modifiers): - if self.mobject.is_point_touching(point): - self.mobject.move_to(point) - return False + def mob_on_mouse_drag(self, point, d_point, buttons, modifiers): + self.mobject.move_to(point) + return False class Button(Mobject): @@ -44,19 +42,17 @@ class Button(Mobject): Pass any mobject and register an on_click method """ - CONFIG = { - "listen_to_events": True - } - def __init__(self, mobject, on_click, **kwargs): super().__init__(**kwargs) - self.mobject, self.on_click = mobject, on_click + self.on_click = on_click + self.mobject = mobject + self.mobject.listen_to_events = True + self.mobject.on_mouse_press = self.mob_on_mouse_press self.add(self.mobject) - def on_mouse_press(self, point, button, mods): - if self.mobject.is_point_touching(point): - self.on_click() - return False + def mob_on_mouse_press(self, point, button, mods): + self.on_click() + return False # Controls @@ -193,6 +189,9 @@ class Checkbox(ContolMobject): class LinearNumberSlider(ContolMobject): CONFIG = { + # Since, only slider circle listnes to drag event + "listen_to_events": False, + "value_type": np.float64, "min_value": -10.0, "max_value": 10.0, @@ -222,6 +221,9 @@ class LinearNumberSlider(ContolMobject): self.slider_axis.set_opacity(0.0) self.slider.move_to(self.slider_axis) + self.slider.listen_to_events = True + self.slider.on_mouse_drag = self.slider_on_mouse_drag + super().__init__(value, self.bar, self.slider, self.slider_axis, ** kwargs) def assert_value(self, value): @@ -231,10 +233,9 @@ class LinearNumberSlider(ContolMobject): prop = (value - self.min_value) / (self.max_value - self.min_value) self.slider.move_to(self.slider_axis.point_from_proportion(prop)) - def on_mouse_drag(self, point, d_point, buttons, modifiers): - if self.slider.is_point_touching(point): - self.set_value(self.get_value_from_point(point)) - return False + def slider_on_mouse_drag(self, point, d_point, buttons, modifiers): + self.set_value(self.get_value_from_point(point)) + return False # Helper Methods @@ -371,6 +372,8 @@ class Textbox(ContolMobject): digest_config(self, kwargs) self.isActive = self.isInitiallyActive self.box = Rectangle(**self.box_kwargs) + self.box.listen_to_events = True + self.box.on_mouse_press = self.box_on_mouse_press self.text = Text(value, **self.text_kwargs) super().__init__(value, self.box, self.text, **kwargs) self.update_text(value) @@ -397,11 +400,10 @@ class Textbox(ContolMobject): else: self.box.set_stroke(self.deactive_color) - def on_mouse_press(self, point, button, mods): - if self.box.is_point_touching(point): - self.isActive = not self.isActive - self.active_anim(self.isActive) - return False + def box_on_mouse_press(self, point, button, mods): + self.isActive = not self.isActive + self.active_anim(self.isActive) + return False def on_key_press(self, symbol, modifiers): char = chr(symbol) @@ -425,7 +427,6 @@ class Textbox(ContolMobject): class ControlPanel(Group): CONFIG = { - "listen_to_events": True, "panel_kwargs": { "width": FRAME_WIDTH / 4, "height": MED_SMALL_BUFF + FRAME_HEIGHT, @@ -451,6 +452,8 @@ class ControlPanel(Group): self.panel = Rectangle(**self.panel_kwargs) self.panel.to_corner(UP + LEFT, buff=0) self.panel.shift(self.panel.get_height() * UP) + self.panel.listen_to_events = True + self.panel.on_mouse_scroll = self.panel_on_mouse_scroll self.panel_opener_rect = Rectangle(**self.opener_kwargs) self.panel_info_text = Text(**self.opener_text_kwargs) @@ -458,6 +461,8 @@ class ControlPanel(Group): self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text) self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN) + self.panel_opener.listen_to_events = True + self.panel_opener.on_mouse_drag = self.panel_opener_on_mouse_drag self.controls = Group(*controls) self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN) @@ -510,14 +515,12 @@ class ControlPanel(Group): self.move_panel_and_controls_to_panel_opener() return self - def on_mouse_drag(self, point, d_point, buttons, modifiers): - if self.panel_opener.is_point_touching(point): - self.panel_opener.match_y(Dot(point)) - self.move_panel_and_controls_to_panel_opener() - return False + def panel_opener_on_mouse_drag(self, point, d_point, buttons, modifiers): + self.panel_opener.match_y(Dot(point)) + self.move_panel_and_controls_to_panel_opener() + return False - def on_mouse_scroll(self, point, offset): - if self.panel.is_point_touching(point): - factor = 10 * offset[1] - self.controls.set_y(self.controls.get_y() + factor) - return False + def panel_on_mouse_scroll(self, point, offset): + factor = 10 * offset[1] + self.controls.set_y(self.controls.get_y() + factor) + return False diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 293b7a71..f15aaa3a 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -58,6 +58,9 @@ class Scene(object): self.mouse_point = Point() self.mouse_drag_point = Point() + self.mob_listners = [] + self.mobjects_to_drag = [] + # Much nicer to work with deterministic scenes if self.random_seed is not None: random.seed(self.random_seed) @@ -205,6 +208,9 @@ class Scene(object): """ self.remove(*new_mobjects) self.mobjects += new_mobjects + for new_mob in new_mobjects: + for mob_listner in filter(lambda mob: mob.listen_to_events, reversed(new_mob.get_family())): + self.mob_listners.insert(0, mob_listner) return self def add_mobjects_among(self, values): @@ -232,6 +238,9 @@ class Scene(object): def bring_to_back(self, *mobjects): self.remove(*mobjects) self.mobjects = list(mobjects) + self.mobjects + for new_mob in reversed(mobjects): + for mob_listner in filter(lambda mob: mob.listen_to_events, reversed(new_mob.get_family())): + self.mob_listners.append(mob_listner) return self def clear(self): @@ -519,10 +528,7 @@ class Scene(object): in reversed order. So the top most mobject's event is called first. This helps in event bubbling. """ - return filter( - lambda mob: mob.listen_to_events, - reversed(self.get_mobject_family_members()) - ) + return self.mob_listners def on_mouse_motion(self, point, d_point): self.mouse_point.move_to(point) @@ -548,13 +554,16 @@ class Scene(object): def on_mouse_drag(self, point, d_point, buttons, modifiers): self.mouse_drag_point.move_to(point) - for mob_listener in self.get_event_listeners_mobjects(): - if mob_listener.is_point_touching(point): - propagate_event = mob_listener.on_mouse_drag(point, d_point, buttons, modifiers) - if propagate_event is not None and propagate_event is False: - return + for mob_listener in self.mobjects_to_drag: + propagate_event = mob_listener.on_mouse_drag(point, d_point, buttons, modifiers) + if propagate_event is not None and propagate_event is False: + return def on_mouse_press(self, point, button, mods): + for mob_listener in self.get_event_listeners_mobjects(): + if mob_listener.is_point_touching(point): + self.mobjects_to_drag.append(mob_listener) + for mob_listener in self.get_event_listeners_mobjects(): if mob_listener.is_point_touching(point): propagate_event = mob_listener.on_mouse_press(point, button, mods) @@ -562,6 +571,8 @@ class Scene(object): return def on_mouse_release(self, point, button, mods): + self.mobjects_to_drag = [] + for mob_listener in self.get_event_listeners_mobjects(): if mob_listener.is_point_touching(point): propagate_event = mob_listener.on_mouse_release(point, button, mods)