Interactive Mobjects Performance Improvements
diff --git a/example_scenes.py b/example_scenes.py
index d8b7d4b247..f2a95727e4 100644
--- a/example_scenes.py
+++ b/example_scenes.py
@@ -497,7 +497,8 @@ def text_updater(old_text):
         self.add(MotionMobject(text))
 
         self.textbox.set_value("Manim")
-        self.embed()
+        # self.wait(60)
+        # self.embed()
 
 
 # See https://github.com/3b1b/videos for many, many more
diff --git a/manimlib/mobject/interactive.py b/manimlib/mobject/interactive.py
index 7e72d8160e..bb27c99ef7 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,24 +42,22 @@ 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
 
-class ContolMobject(ValueTracker):
+class ControlMobject(ValueTracker):
     CONFIG = {
         "listen_to_events": True
     }
@@ -88,7 +84,7 @@ def set_value_anim(self, value):
         pass
 
 
-class EnableDisableButton(ContolMobject):
+class EnableDisableButton(ControlMobject):
     CONFIG = {
         "value_type": np.dtype(bool),
         "rect_kwargs": {
@@ -122,7 +118,7 @@ def on_mouse_press(self, point, button, mods):
         return False
 
 
-class Checkbox(ContolMobject):
+class Checkbox(ControlMobject):
     CONFIG = {
         "value_type": np.dtype(bool),
         "rect_kwargs": {
@@ -191,8 +187,11 @@ def get_cross(self):
         return cross
 
 
-class LinearNumberSlider(ContolMobject):
+class LinearNumberSlider(ControlMobject):
     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 @@ def __init__(self, value=0, **kwargs):
         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 @@ def set_value_anim(self, value):
         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
 
@@ -348,7 +349,7 @@ def get_picked_opacity(self):
         return rgba[3]
 
 
-class Textbox(ContolMobject):
+class Textbox(ControlMobject):
     CONFIG = {
         "value_type": np.dtype(object),
 
@@ -371,6 +372,8 @@ def __init__(self, value="", **kwargs):
         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 @@ def active_anim(self, isActive):
         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 @@ def on_key_press(self, symbol, modifiers):
 
 class ControlPanel(Group):
     CONFIG = {
-        "listen_to_events": True,
         "panel_kwargs": {
             "width": FRAME_WIDTH / 4,
             "height": MED_SMALL_BUFF + FRAME_HEIGHT,
@@ -451,6 +452,8 @@ def __init__(self, *controls, **kwargs):
         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 @@ def __init__(self, *controls, **kwargs):
 
         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 @@ def close_panel(self):
         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 293b7a7195..f15aaa3a85 100644
--- a/manimlib/scene/scene.py
+++ b/manimlib/scene/scene.py
@@ -58,6 +58,9 @@ def __init__(self, **kwargs):
         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 @@ def add(self, *new_mobjects):
         """
         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 @@ def bring_to_front(self, *mobjects):
     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 @@ def get_event_listeners_mobjects(self):
             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 @@ def on_mouse_motion(self, point, d_point):
     def on_mouse_drag(self, point, d_point, buttons, modifiers):
         self.mouse_drag_point.move_to(point)
 
+        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):
-                propagate_event = mob_listener.on_mouse_drag(point, d_point, buttons, modifiers)
-                if propagate_event is not None and propagate_event is False:
-                    return
+                self.mobjects_to_drag.append(mob_listener)
 
-    def on_mouse_press(self, point, button, mods):
         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 @@ def on_mouse_press(self, point, button, mods):
                     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)

# Motivation
As mentioned in https://github.com/3b1b/manim/pull/1323#issuecomment-769350572, interactive mobjects were a bit laggy.

So, to improve interactively mobjects performance, the following changes are made.
1. Maintain a separate list of mobject listeners instead of filtering mobjects on every frame.
>   This significantly improved the performance on all events except the on_mouse_drag event. So to improve it the 2nd change was made.
2.  Changed the way drag events are handled.
> This improved the performance of on_mouse_drag event.

# Files Changed
1. manimlib/scene/scene.py
> To store a separate list of mob_listners rather than checking it on every frame and changed the on_mouse_drag event handling. 
2. manimlib/mobject/interactive.py
> To adapt to the changes made in the way event handling works
3. example_scenes.py
> Commented out "self.embed()"

# Change in how on_mouse_drag works
If mouse is being draged, following events are triggered
1. **on_mouse_press** once
2. Then, till the mouse is being dragged, **on_mouse_drag** is called
3. Finally, **on_mouse_release** once

Previously, on_mouse_drag event of mobject was called on every frame if the point is touching the mobject.

So, if in between dragging, the mouse point leaves the mobject radius, on_mouse_drag event of mobject is stopped.

Now, If on_mouse_press is called, all the mobjects near mouse point is stored in array. And when mouse is being dragged,
on_mouse_drag is called on all the mobjects stored earlier regardless of whether mouse point is touching the mobject or not.
And, once on_mouse_release is called, that array is cleared

This has significantly improved the interactivity involving dragging.

# Results
![DraggableMobjectsExample](https://user-images.githubusercontent.com/49190295/106261792-1423f400-6248-11eb-8738-4980cec4b19d.gif)
Now, all the interactive mobjects are responding very quickly to events.