Repository: ManimCommunity/manim
Pull request: Fading module enhancements
## Changelog / Overview
<!--changelog-start-->
Moved functionality of all Fading classes to :class:`~.FadeIn` and :class:`~.FadeOut`. All other fading classes have been deprecated. 
<!--changelog-end-->

## Motivation
Closes #1426

## Explanation for Changes
Have a look at the docs. If that doesn't answer all your questions about how this feature works, I should probably enhance them, not this PR description.
https://manimce--1454.org.readthedocs.build/en/1454/reference/manim.animation.fading.html

## Testing Status
Tests are working.

## Checklist
- [x] I have read the [Contributing Guidelines](https://docs.manim.community/en/latest/contributing.html)
- [x] I have written a descriptive PR title (see top of PR template for examples)
- [x] I have written a changelog entry for the PR or deem it unnecessary
- [x] My new functions/classes either have a docstring or are private
- [x] My new functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [x] My new documentation builds, looks correctly formatted, and adds no additional build warnings
<!-- Once again, thanks for contributing to ManimCommunity! -->


<!-- Do not modify the lines below. These are for the reviewers of your PR -->
## Reviewer Checklist
- [ ] The PR title is descriptive enough
- [ ] The PR is labeled correctly
- [ ] The changelog entry is completed if necessary
- [ ] Newly added functions/classes either have a docstring or are private
- [ ] Newly added functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [ ] Newly added documentation builds, looks correctly formatted, and adds no additional build warnings


Problem statement:
Fading Module: Removing the clutter
## Enhancement proposal
At the moment, the animation.fading module contains 10 Animation clsses:

`FadeIn`, `FadeInFrom`, `FadeInFromLarge`, `FadeInFromPoint`, `VFadeIn`, `FadeOut`, `FadeOutAndShift`, `FadeOutToPoint`, `VFadeInThenOut`, `VFadeOut`

I think `FadeIn` and `FadeOut` is enough. Let me explain:
Basically there are 3 distinctions: Direction(In or Out), Movement (shift, specific position, None) and Scaling (grow, shrink, None)
FadeIn and FadeOut need three new parameters:
`FadeIn(..., scaling=1, shift=None, from_position=None)` and `FadeOut(..., to_position=None)`

This doesn't only reduce the clutter of too many classes, it also shows a lot more clearly, what the Animation actually does:
```
Direction   Scaling     Shifting    Animation
In          shrink      shift       
Out         shrink      shift       
In          grow        shift       
Out         grow        shift       
In          -           shift       FadeInFrom
Out         -           shift       FadeOutAndShift
In          shrink      position    
Out         shrink      position    
In          grow        position    FadeInFromPoint
Out         grow        position    
In          -           position    
Out         -           position    FadeOutToPoint
In          shrink      -           
Out         shrink      -           
In          grow        -           FadeInFromLarge
Out         grow        -           
In          -           -           FadeIn
Out         -           -           FadeOut
```

Why are `FadeInFrom` and `FadeOutAndShift` named so differently? Why does `FadeInFromPoint` scale the Mobject but `FadeOutToPoint` doesn't? Why are there these specific classes, but others not (a personal favorite would be `FadeOutAndShrinkToPointWhileShifting` :smile: )?

And about the `VFade...` Animations (they are supposed to be used for VMobjects, who knew?): They should simply be deprecated, they have no use as far as I can tell.

Since two classes would be a bit lonely in their own module, I would like to let them join the other creation animations :slightly_smiling_face: 



## Additional comments
100 points go to the person naming the remaining 11 Animations :laughing:

Hints from issue discussion:

Commit message: Merge branch 'master' into fading-overhaul

Patch to review:
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index 742eb5ffa1..ea198961c1 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -450,12 +450,12 @@ Special Camera Settings
 
             frame_text.next_to(frame, DOWN)
 
-            self.play(Create(frame), FadeInFrom(frame_text, direction=DOWN))
+            self.play(Create(frame), FadeIn(frame_text, shift=UP))
             self.activate_zooming()
 
             self.play(self.get_zoomed_display_pop_out_animation(), unfold_camera)
             zoomed_camera_text.next_to(zoomed_display_frame, DOWN)
-            self.play(FadeInFrom(zoomed_camera_text, direction=DOWN))
+            self.play(FadeIn(zoomed_camera_text, shift=UP))
             # Scale in        x   y  z
             scale_factor = [0.5, 1.5, 0]
             self.play(
@@ -576,7 +576,7 @@ Advanced Projects
 =================
 
 .. manim:: OpeningManim
-    :ref_classes: Tex MathTex Write FadeInFrom LaggedStart NumberPlane Create
+    :ref_classes: Tex MathTex Write FadeIn LaggedStart NumberPlane Create
     :ref_methods: NumberPlane.prepare_for_nonlinear_transform
 
     class OpeningManim(Scene):
@@ -586,7 +586,7 @@ Advanced Projects
             VGroup(title, basel).arrange(DOWN)
             self.play(
                 Write(title),
-                FadeInFrom(basel, UP),
+                FadeIn(basel, shift=DOWN),
             )
             self.wait()
 
@@ -594,7 +594,7 @@ Advanced Projects
             transform_title.to_corner(UP + LEFT)
             self.play(
                 Transform(title, transform_title),
-                LaggedStart(*[FadeOutAndShift(obj, direction=DOWN) for obj in basel]),
+                LaggedStart(*[FadeOut(obj, shift=DOWN) for obj in basel]),
             )
             self.wait()
 
@@ -606,7 +606,7 @@ Advanced Projects
             self.add(grid, grid_title)  # Make sure title is on top of grid
             self.play(
                 FadeOut(title),
-                FadeInFrom(grid_title, direction=DOWN),
+                FadeIn(grid_title, shift=UP),
                 Create(grid, run_time=3, lag_ratio=0.1),
             )
             self.wait()
diff --git a/docs/source/reference.rst b/docs/source/reference.rst
index a3f1fbe53c..7a4c5ead9e 100644
--- a/docs/source/reference.rst
+++ b/docs/source/reference.rst
@@ -123,6 +123,7 @@ Utilities
    ~utils.bezier
    ~utils.color
    ~utils.config_ops
+   ~utils.deprecation
    ~utils.hashing
    ~utils.ipython_magic
    ~utils.images
diff --git a/example_scenes/basic.py b/example_scenes/basic.py
index 7745402a14..4e4a158948 100644
--- a/example_scenes/basic.py
+++ b/example_scenes/basic.py
@@ -21,7 +21,7 @@ def construct(self):
         VGroup(title, basel).arrange(DOWN)
         self.play(
             Write(title),
-            FadeInFrom(basel, UP),
+            FadeIn(basel, shift=DOWN),
         )
         self.wait()
 
@@ -29,7 +29,7 @@ def construct(self):
         transform_title.to_corner(UP + LEFT)
         self.play(
             Transform(title, transform_title),
-            LaggedStart(*[FadeOutAndShift(obj, direction=DOWN) for obj in basel]),
+            LaggedStart(*[FadeOut(obj, shift=DOWN) for obj in basel]),
         )
         self.wait()
 
@@ -41,7 +41,7 @@ def construct(self):
         self.add(grid, grid_title)  # Make sure title is on top of grid
         self.play(
             FadeOut(title),
-            FadeInFrom(grid_title, direction=DOWN),
+            FadeIn(grid_title, direction=UP),
             Create(grid, run_time=3, lag_ratio=0.1),
         )
         self.wait()
diff --git a/manim/animation/creation.py b/manim/animation/creation.py
index a0b2415543..940d4b81f4 100644
--- a/manim/animation/creation.py
+++ b/manim/animation/creation.py
@@ -88,6 +88,7 @@ def construct(self):
 from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
 from ..mobject.types.vectorized_mobject import VMobject
 from ..utils.bezier import integer_interpolate
+from ..utils.deprecation import deprecated
 from ..utils.rate_functions import double_smooth, linear, smooth
 
 
@@ -160,13 +161,11 @@ def _get_bounds(self, alpha: float) -> Tuple[int, float]:
         return (0, alpha)
 
 
+@deprecated(since="0.5.0", until="0.7.0", replacement="Create")
 class ShowCreation(Create):
     """Deprecated. Use :class:`~.Create` instead."""
 
     def __init__(self, mobject: VMobject, lag_ratio: float = 1.0, **kwargs) -> None:
-        logger.warning(
-            "ShowCreation has been deprecated in favor of Create. Please use Create instead!"
-        )
         super().__init__(mobject, lag_ratio=lag_ratio, **kwargs)
 
     def _get_bounds(self, alpha: float) -> Tuple[int, float]:
diff --git a/manim/animation/fading.py b/manim/animation/fading.py
index f38d5cdba9..37c5bc975f 100644
--- a/manim/animation/fading.py
+++ b/manim/animation/fading.py
@@ -1,54 +1,14 @@
 """Fading in and out of view.
 
-.. manim:: Example
-    :hide_source:
+.. manim:: Fading
 
-    class Example(Scene):
+    class Fading(Scene):
         def construct(self):
-            s1 = Square().set_color(BLUE)
-            s2 = Square().set_color(BLUE)
-            s3 = Square().set_color(BLUE)
-            s4 = Square().set_color(BLUE)
-            s5 = Square().set_color(BLUE)
-            s6 = Square().set_color(RED)
-            s7 = Square().set_color(RED)
-            s8 = Square().set_color(RED)
-            VGroup(s1, s2, s3, s4).set_x(0).arrange(buff=1.9).shift(UP)
-            VGroup(s5, s6, s7, s8).set_x(0).arrange(buff=1.9).shift(2 * DOWN)
-            t1 = Text("FadeIn").scale(0.5).next_to(s1, UP)
-            t2 = Text("FadeInFromPoint").scale(0.5).next_to(s2, UP)
-            t3 = Text("FadeInFrom").scale(0.5).next_to(s3, UP)
-            t4 = Text("VFadeIn").scale(0.5).next_to(s4, UP)
-            t5 = Text("FadeInFromLarge").scale(0.4).next_to(s5, UP)
-            t6 = Text("FadeOut").scale(0.45).next_to(s6, UP)
-            t7 = Text("FadeOutAndShift").scale(0.45).next_to(s7, UP)
-            t8 = Text("VFadeOut").scale(0.45).next_to(s8, UP)
-
-            objs = [ManimBanner().scale(0.25) for _ in range(1, 9)]
-            objs[0].move_to(s1.get_center())
-            objs[1].move_to(s2.get_center())
-            objs[2].move_to(s3.get_center())
-            objs[3].move_to(s4.get_center())
-            objs[4].move_to(s5.get_center())
-            objs[5].move_to(s6.get_center())
-            objs[6].move_to(s7.get_center())
-            objs[7].move_to(s8.get_center())
-            self.add(s1, s2, s3, s4, s5, s6, s7, s8, t1, t2, t3, t4, t5, t6, t7, t8)
-            self.add(*objs)
-
-            self.play(
-                FadeIn(objs[0]),
-                FadeInFromPoint(objs[1], s2.get_center()),
-                FadeInFrom(objs[2], LEFT*0.2),
-                VFadeIn(objs[3]),
-                FadeInFromLarge(objs[4]),
-            )
-            self.wait(0.3)
-            self.play(
-                FadeOut(objs[5]),
-                FadeOutAndShift(objs[6], DOWN),
-                VFadeOut(objs[7])
-            )
+            tex_in = Tex("Fade", "In").scale(3)
+            tex_out = Tex("Fade", "Out").scale(3)
+            self.play(FadeIn(tex_in, shift=DOWN, scale=0.66))
+            self.play(ReplacementTransform(tex_in, tex_out))
+            self.play(FadeOut(tex_out, shift=DOWN * 2, scale=1.5))
 
 """
 
@@ -66,191 +26,253 @@ def construct(self):
     "VFadeInThenOut",
 ]
 
-import typing
+from typing import Callable, Optional, Union
 
 import numpy as np
 
-from .. import logger
-from ..animation.animation import Animation
 from ..animation.transform import Transform
 from ..constants import DOWN, ORIGIN
-from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
-from ..mobject.types.vectorized_mobject import VMobject
-from ..utils.bezier import interpolate
+from ..mobject.mobject import Mobject
+from ..scene.scene import Scene
+from ..utils.deprecation import deprecated
 from ..utils.rate_functions import there_and_back
 
-if typing.TYPE_CHECKING:
-    from ..mobject.mobject import Mobject
-    from ..scene.scene import Scene
 
-DEFAULT_FADE_LAG_RATIO: float = 0
+class _Fade(Transform):
+    """Fade in a :class:`~.Mobject` in or out.
 
+    Parameters
+    ----------
+    mobject
+        The mobject to be faded in.
+    shift
+        The vector by which the mobject shifts while beeing faded.
+    target_position
+        The position to/from which the mobject moves while beeing faded in. In case
+        another mobject is given as target position, its center is used.
+    scale
+        The factor by which the mobject is scaled initially before beeing rescaling to
+        its original size while beeing faded in.
 
-class FadeOut(Transform):
-    """A transform fading out the given mobject."""
-
-    def __init__(
-        self,
-        vmobject: typing.Union[VMobject, OpenGLVMobject],
-        remover: bool = True,
-        lag_ratio: float = DEFAULT_FADE_LAG_RATIO,
-        **kwargs
-    ) -> None:
-        super().__init__(vmobject, remover=remover, lag_ratio=lag_ratio, **kwargs)
-
-    def create_target(self) -> "Mobject":
-        return self.mobject.copy().fade(1)
-
-    def clean_up_from_scene(self, scene: "Scene" = None) -> None:
-        super().clean_up_from_scene(scene)
-        self.interpolate(0)
-
+    """
 
-class FadeIn(Transform):
     def __init__(
         self,
-        vmobject: typing.Union[VMobject, OpenGLVMobject],
-        lag_ratio: float = DEFAULT_FADE_LAG_RATIO,
+        mobject: Mobject,
+        shift: Optional[np.ndarray] = None,
+        target_position: Optional[Union[np.ndarray, Mobject]] = None,
+        scale: float = 1,
         **kwargs
     ) -> None:
-        super().__init__(vmobject, lag_ratio=lag_ratio, **kwargs)
+        self.point_target = False
+        if shift is None:
+            if target_position is not None:
+                if isinstance(target_position, Mobject):
+                    target_position = target_position.get_center()
+                shift = target_position - mobject.get_center()
+                self.point_target = True
+            else:
+                shift = ORIGIN
+        self.shift_vector = shift
+        self.scale_factor = scale
+        super().__init__(mobject, **kwargs)
 
-    def create_target(self) -> "Mobject":
-        return self.mobject
+    def _create_faded_mobject(self, fadeIn: bool) -> Mobject:
+        """Create a faded, shifted and scaled copy of the mobject.
 
-    def create_starting_mobject(self) -> "Mobject":
-        start = super().create_starting_mobject()
-        start.fade(1)
-        if isinstance(start, (VMobject, OpenGLVMobject)):
-            start.set_stroke(opacity=0)
-            start.set_fill(opacity=0)
-        return start
+        Parameters
+        ----------
+        fadeIn
+            Whether the faded mobject is used to fade in.
 
+        Returns
+        -------
+        Mobject
+            The faded, shifted and scaled copy of the mobject.
+        """
+        faded_mobject = self.mobject.copy()
+        faded_mobject.set_opacity(0)
+        direction_modifier = -1 if fadeIn and not self.point_target else 1
+        faded_mobject.shift(self.shift_vector * direction_modifier)
+        faded_mobject.scale(self.scale_factor)
+        return faded_mobject
 
-class FadeInFrom(Transform):
-    def __init__(
-        self, mobject: "Mobject", direction: np.ndarray = DOWN, **kwargs
-    ) -> None:
-        self.direction = direction
-        super().__init__(mobject, **kwargs)
 
-    def create_target(self) -> "Mobject":
-        return self.mobject.copy()
+class FadeIn(_Fade):
+    """Fade in a :class:`~.Mobject`.
 
-    def begin(self) -> None:
-        super().begin()
-        self.starting_mobject.shift(self.direction)
-        self.starting_mobject.fade(1)
+    Parameters
+    ----------
+    mobject
+        The mobject to be faded in.
+    shift
+        The vector by which the mobject shifts while beeing faded in.
+    target_position
+        The position from which the mobject starts while beeing faded in. In case
+        another mobject is given as target position, its center is used.
+    scale
+        The factor by which the mobject is scaled initially before beeing rescaling to
+        its original size while beeing faded in.
+
+    Examples
+    --------
+
+    .. manim :: FadeInExample
+
+        class FadeInExample(Scene):
+            def construct(self):
+                dot = Dot(UP * 2 + LEFT)
+                self.add(dot)
+                tex = Tex(
+                    "FadeIn with ", "shift ", " or target\\_position", " and scale"
+                ).scale(1)
+                animations = [
+                    FadeIn(tex[0]),
+                    FadeIn(tex[1], shift=DOWN),
+                    FadeIn(tex[2], target_position=dot),
+                    FadeIn(tex[3], scale=1.5),
+                ]
+                self.play(AnimationGroup(*animations, lag_ratio=0.5))
 
+    """
 
-class FadeOutAndShift(FadeOut):
-    def __init__(
-        self, mobject: "Mobject", direction: np.ndarray = DOWN, **kwargs
-    ) -> None:
-        self.direction = direction
-        super().__init__(mobject, **kwargs)
+    def create_target(self):
+        return self.mobject
 
-    def create_target(self) -> "Mobject":
-        target = super().create_target()
-        target.shift(self.direction)
-        return target
+    def create_starting_mobject(self):
+        return self._create_faded_mobject(fadeIn=True)
 
 
-class FadeOutToPoint(FadeOut):
-    """Fades out a mobject while moving it to a specified point.
+class FadeOut(_Fade):
+    """Fade out a :class:`~.Mobject`.
 
     Parameters
     ----------
     mobject
-        The :class:`~.Mobject` to be animated.
-    point
-        Either a point or another :class:`~.Mobject` to whose center the
-        Mobject is moved in the animation. Defaults to ``ORIGIN``.
-    kwargs
-        Further keyword arguments are passed to the parent class.
+        The mobject to be faded out.
+    shift
+        The vector by which the mobject shifts while beeing faded out.
+    target_position
+        The position to which the mobject moves while beeing faded out. In case another
+        mobject is given as target position, its center is used.
+    scale
+        The factor by which the mobject is scaled while beeing faded out.
+
+    Examples
+    --------
+
+    .. manim :: FadeInExample
+
+        class FadeInExample(Scene):
+            def construct(self):
+                dot = Dot(UP * 2 + LEFT)
+                self.add(dot)
+                tex = Tex(
+                    "FadeOut with ", "shift ", " or target\\_position", " and scale"
+                ).scale(1)
+                animations = [
+                    FadeOut(tex[0]),
+                    FadeOut(tex[1], shift=DOWN),
+                    FadeOut(tex[2], target_position=dot),
+                    FadeOut(tex[3], scale=0.5),
+                ]
+                self.play(AnimationGroup(*animations, lag_ratio=0.5))
+
 
     """
 
-    def __init__(
-        self,
-        mobject: "Mobject",
-        point: typing.Union["Mobject", np.ndarray] = ORIGIN,
-        **kwargs
-    ) -> None:
-        self.point = point
-        super().__init__(mobject, **kwargs)
+    def __init__(self, mobject: Mobject, **kwargs) -> None:
+        super().__init__(mobject, remover=True, **kwargs)
 
-    def create_target(self) -> "Mobject":
-        """Creates the target of the animation."""
-        target = super().create_target()
-        target.move_to(self.point)
-        return target
+    def create_target(self):
+        return self._create_faded_mobject(fadeIn=False)
 
+    def clean_up_from_scene(self, scene: Scene = None) -> None:
+        super().clean_up_from_scene(scene)
+        self.interpolate(0)
 
-class FadeInFromPoint(FadeIn):
+
+@deprecated(
+    since="v0.6.0",
+    until="v0.8.0",
+    replacement="FadeIn",
+    message="You can set a shift amount there.",
+)
+class FadeInFrom(FadeIn):
     def __init__(
-        self, mobject: "Mobject", point: typing.Union["Mobject", np.ndarray], **kwargs
+        self, mobject: "Mobject", direction: np.ndarray = DOWN, **kwargs
     ) -> None:
-        self.point = point
-        super().__init__(mobject, **kwargs)
-
-    def create_starting_mobject(self) -> "Mobject":
-        start = super().create_starting_mobject()
-        start.scale(0)
-        start.move_to(self.point)
-        return start
+        super().__init__(mobject, shift=-direction, **kwargs)
 
 
-class FadeInFromLarge(FadeIn):
-    def __init__(self, mobject: "Mobject", scale_factor: float = 2, **kwargs) -> None:
-        self.scale_factor = scale_factor
-        super().__init__(mobject, **kwargs)
+@deprecated(
+    since="v0.6.0",
+    until="v0.8.0",
+    replacement="FadeOut",
+    message="You can set a shift amount there.",
+)
+class FadeOutAndShift(FadeIn):
+    def __init__(
+        self, mobject: "Mobject", direction: np.ndarray = DOWN, **kwargs
+    ) -> None:
+        super().__init__(mobject, shift=direction, **kwargs)
 
-    def create_starting_mobject(self) -> "Mobject":
-        start = super().create_starting_mobject()
-        start.scale(self.scale_factor)
-        return start
 
+@deprecated(
+    since="v0.6.0",
+    until="v0.8.0",
+    replacement="FadeOut",
+    message="You can set a target position there.",
+)
+class FadeOutToPoint(FadeOut):
+    def __init__(
+        self, mobject: "Mobject", point: Union["Mobject", np.ndarray] = ORIGIN, **kwargs
+    ) -> None:
+        super().__init__(mobject, target_position=point, **kwargs)
 
-class VFadeIn(Animation):
-    """
-    VFadeIn and VFadeOut only work for VMobjects,
-    """
 
+@deprecated(
+    since="v0.6.0",
+    until="v0.8.0",
+    replacement="FadeIn",
+    message="You can set a target position and scaling factor there.",
+)
+class FadeInFromPoint(FadeIn):
     def __init__(
-        self, mobject: "Mobject", suspend_mobject_updating: bool = False, **kwargs
+        self, mobject: "Mobject", point: Union["Mobject", np.ndarray], **kwargs
     ) -> None:
-        super().__init__(
-            mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs
-        )
+        super().__init__(mobject, target_position=point, scale=0, **kwargs)
 
-    def interpolate_submobject(
-        self, submobject: "Mobject", starting_submobject: "Mobject", alpha: float
-    ) -> None:
-        submobject.set_stroke(
-            opacity=interpolate(0, starting_submobject.get_stroke_opacity(), alpha)
-        )
-        submobject.set_fill(
-            opacity=interpolate(0, starting_submobject.get_fill_opacity(), alpha)
-        )
 
+@deprecated(
+    since="v0.6.0",
+    until="v0.8.0",
+    replacement="FadeIn",
+    message="You can set a scaling factor there.",
+)
+class FadeInFromLarge(FadeIn):
+    def __init__(self, mobject: "Mobject", scale_factor: float = 2, **kwargs) -> None:
+        super().__init__(mobject, scale=scale_factor, **kwargs)
 
-class VFadeOut(VFadeIn):
-    def __init__(self, mobject: "Mobject", remover: bool = True, **kwargs) -> None:
-        super().__init__(mobject, remover=remover, **kwargs)
 
-    def interpolate_submobject(
-        self, submobject: "Mobject", starting_submobject: "Mobject", alpha: float
-    ) -> None:
-        super().interpolate_submobject(submobject, starting_submobject, 1 - alpha)
+@deprecated(since="v0.6.0", until="v0.8.0", replacement="FadeIn")
+class VFadeIn(FadeIn):
+    def __init__(self, mobject: "Mobject", **kwargs) -> None:
+        super().__init__(mobject, **kwargs)
+
+
+@deprecated(since="v0.6.0", until="v0.8.0", replacement="FadeOut")
+class VFadeOut(FadeOut):
+    def __init__(self, mobject: "Mobject", **kwargs) -> None:
+        super().__init__(mobject, **kwargs)
 
 
-class VFadeInThenOut(VFadeIn):
+@deprecated(since="v0.6.0", until="v0.8.0")
+class VFadeInThenOut(FadeIn):
     def __init__(
         self,
         mobject: "Mobject",
-        remover: bool = True,
-        rate_func: typing.Callable[[float, float], float] = there_and_back,
+        rate_func: Callable[[float], float] = there_and_back,
         **kwargs
     ):
-        super().__init__(mobject, remover=remover, rate_func=rate_func, **kwargs)
+        super().__init__(mobject, remover=True, rate_func=rate_func, **kwargs)
diff --git a/manim/animation/indication.py b/manim/animation/indication.py
index 7bca1477d9..e5f63e863b 100644
--- a/manim/animation/indication.py
+++ b/manim/animation/indication.py
@@ -19,8 +19,8 @@ def construct(self):
                 else:
                     self.play(indications[i](names[i]))
                 self.play(AnimationGroup(
-                    FadeOutAndShift(names[i], UP*1.5),
-                    FadeInFrom(names[(i+1)%len(names)], DOWN*1.5),
+                    FadeOut(names[i], shift=UP*1.5),
+                    FadeIn(names[(i+1)%len(names)], shift=UP*1.5),
                 ))
 
 """
diff --git a/manim/animation/transform_matching_parts.py b/manim/animation/transform_matching_parts.py
index 2de9dcb5a9..96d04d1b77 100644
--- a/manim/animation/transform_matching_parts.py
+++ b/manim/animation/transform_matching_parts.py
@@ -14,7 +14,7 @@
 from ..mobject.types.opengl_vectorized_mobject import OpenGLVGroup, OpenGLVMobject
 from ..mobject.types.vectorized_mobject import VGroup, VMobject
 from .composition import AnimationGroup
-from .fading import FadeInFromPoint, FadeOutToPoint
+from .fading import FadeIn, FadeOut
 from .transform import FadeTransformPieces, Transform
 
 
@@ -125,11 +125,9 @@ def __init__(
         if fade_transform_mismatches:
             anims.append(FadeTransformPieces(fade_source, fade_target, **kwargs))
         else:
+            anims.append(FadeOut(fade_source, target_position=fade_target, **kwargs))
             anims.append(
-                FadeOutToPoint(fade_source, fade_target.get_center(), **kwargs)
-            )
-            anims.append(
-                FadeInFromPoint(fade_target.copy(), fade_source.get_center(), **kwargs)
+                FadeIn(fade_target.copy(), target_position=fade_target, **kwargs)
             )
 
         super().__init__(*anims)
diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py
index 7bc691c528..cc24ae194f 100644
--- a/manim/mobject/types/vectorized_mobject.py
+++ b/manim/mobject/types/vectorized_mobject.py
@@ -1609,7 +1609,7 @@ def construct(self):
                 self.play(FadeOut(my_dict["c"]))
                 self.wait()
 
-                self.play(FadeOutAndShift(my_dict["r"], DOWN))
+                self.play(FadeOut(my_dict["r"], shift=DOWN))
                 self.wait()
 
                 # you can also make a VDict from an existing dict of mobjects
diff --git a/manim/utils/deprecation.py b/manim/utils/deprecation.py
new file mode 100644
index 0000000000..cbb8601851
--- /dev/null
+++ b/manim/utils/deprecation.py
@@ -0,0 +1,467 @@
+"""Decorators for deprecating classes, functions and function parameters."""
+
+__all__ = ["deprecated", "deprecated_params"]
+
+
+import inspect
+import re
+from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
+
+from decorator import decorate, decorator
+
+from .. import logger
+
+
+def _get_callable_info(callable: Callable) -> Tuple[str, str]:
+    """Returns type and name of a callable.
+
+    Parameters
+    ----------
+    callable
+        The callable
+
+    Returns
+    -------
+    Tuple[str, str]
+        The type and name of the callable. Type can can be one of "class", "method" (for
+        functions defined in classes) or "function"). For methods, name is Class.method.
+    """
+    what = type(callable).__name__
+    name = callable.__qualname__
+    if what == "function":
+        what = "method"
+    elif what == "type":
+        what = "class"
+    return (what, name)
+
+
+def _deprecation_text_component(
+    since: Optional[str], until: Optional[str], message: str
+) -> str:
+    """Generates a text component used in deprecation messages.
+
+    Parameters
+    ----------
+    since
+        The version or date since deprecation
+    until
+        The version or date until removal of the deprecated callable
+    message
+        The reason for why the callable has been deprecated
+
+    Returns
+    -------
+    str
+        The deprecation message text component.
+    """
+    since = f"since {since} " if since else ""
+    until = (
+        f"is expected to be removed after {until}"
+        if until
+        else "may be removed in a later version"
+    )
+    msg = " " + message if message else ""
+    return f"deprecated {since}and {until}.{msg}"
+
+
+def deprecated(
+    func: Callable = None,
+    since: Optional[str] = None,
+    until: Optional[str] = None,
+    replacement: Optional[str] = None,
+    message: Optional[str] = "",
+) -> Callable:
+    """Decorator to mark a callable as deprecated.
+
+    The decorated callable will cause a warning when used. The docstring of the
+    deprecated callable is adjusted to indicate that this callable is deprecated.
+
+    Parameters
+    ----------
+    func
+        The function to be decorated. Should not be set by the user.
+    since
+        The version or date since deprecation.
+    until
+        The version or date until removal of the deprecated callable.
+    replacement
+        The identifier of the callable replacing the deprecated one.
+    message
+        The reason for why the callable has been deprecated.
+
+    Returns
+    -------
+    Callable
+        The decorated callable.
+
+    Examples
+    --------
+    Basic usage::
+
+        @deprecated
+        def foo(**kwargs):
+            pass
+
+        @deprecated
+        class Bar:
+            def __init__(self):
+                pass
+
+            @deprecated
+            def baz(self):
+                pass
+
+        foo()
+        # WARNING  The function foo has been deprecated and may be removed in a later version.
+
+        a = Bar()
+        # WARNING  The class Bar has been deprecated and may be removed in a later version.
+
+        a.baz()
+        # WARNING  The method Bar.baz has been deprecated and may be removed in a later version.
+
+    You can specify additional information for a more precise warning::
+
+        @deprecated(
+            since="v0.2",
+            until="v0.4",
+            replacement="bar",
+            message="It is cooler."
+        )
+        def foo():
+            pass
+
+        foo()
+        # WARNING  The function foo has been deprecated since v0.2 and is expected to be removed after v0.4. Use bar instead. It is cooler.
+
+    You may also use dates instead of versions::
+
+        @deprecated(since="05/01/2021", until="06/01/2021")
+        def foo():
+            pass
+
+        foo()
+        # WARNING  The function foo has been deprecated since 05/01/2021 and is expected to be removed after 06/01/2021.
+
+    """
+    # If used as factory:
+    if func is None:
+        return lambda func: deprecated(func, since, until, replacement, message)
+
+    what, name = _get_callable_info(func)
+
+    def warning_msg(for_docs: bool = False) -> str:
+        """Generate the deprecation warning message.
+
+        Parameters
+        ----------
+        for_docs
+            Whether or not to format the message for use in documentation.
+
+        Returns
+        -------
+        str
+            The deprecation message.
+        """
+        msg = message
+        if replacement is not None:
+            repl = replacement
+            if for_docs:
+                mapper = {"class": "class", "method": "meth", "function": "func"}
+                repl = f":{mapper[what]}:`~.{replacement}`"
+            msg = f"Use {repl} instead.{' ' + message if message else ''}"
+        deprecated = _deprecation_text_component(since, until, msg)
+        return f"The {what} {name} has been {deprecated}"
+
+    def deprecate_docs(func: Callable):
+        """Adjust docstring to indicate the deprecation.
+
+        Parameters
+        ----------
+        func
+            The callable whose docstring to adjust.
+        """
+        warning = warning_msg(True)
+        doc_string = func.__doc__ or ""
+        func.__doc__ = f"{doc_string}\n\n.. admonition:: Deprecated\n  :class: attention\n\n  {warning}"
+
+    def deprecate(func: Callable, *args, **kwargs):
+        """The actual decorator used to extend the callables behavior.
+
+        Logs a warning message.
+
+        Parameters
+        ----------
+        func
+            The callable to decorate.
+        args
+            The arguments passed to the given callable.
+        kwargs
+            The keyword arguments passed to the given callable.
+
+        Returns
+        -------
+        Any
+            The return value of the given callable when beeing passed the given
+            arguments.
+        """
+        logger.warning(warning_msg())
+        return func(*args, **kwargs)
+
+    if type(func) == type:
+        deprecate_docs(func)
+        func.__init__ = decorate(func.__init__, deprecate)
+        return func
+
+    func = decorate(func, deprecate)
+    deprecate_docs(func)
+    return func
+
+
+def deprecated_params(
+    params: Optional[Union[str, Iterable[str]]] = None,
+    since: Optional[str] = None,
+    until: Optional[str] = None,
+    message: Optional[str] = "",
+    redirections: Optional[
+        "Iterable[Union[Tuple[str, str], Callable[..., dict[str, Any]]]]"
+    ] = None,
+) -> Callable:
+    """Decorator to mark parameters of a callable as deprecated.
+
+    It can also be used to automatically redirect deprecated parameter values to their
+    replacements.
+
+    Parameters
+    ----------
+    params
+        The parameters to be deprecated. Can consist of:
+
+        * An iterable of strings, with each element representing a parameter to deprecate
+        * A single string, with parameter names separated by commas or spaces.
+    since
+        The version or date since deprecation.
+    until
+        The version or date until removal of the deprecated callable.
+    message
+        The reason for why the callable has been deprecated.
+    redirections
+        A list of parameter redirections. Each redirection can be one of the following:
+
+        * A tuple of two strings. The first string defines the name of the deprecated
+          parameter; the second string defines the name of the parameter to redirect to,
+          when attempting to use the first string.
+
+        * A function performing the mapping operation. The parameter names of the
+          function determine which parameters are used as input. The function must
+          return a dictionary which contains the redirected arguments.
+
+        Redirected parameters are also implicitly deprecated.
+
+    Returns
+    -------
+    Callable
+        The decorated callable.
+
+    Raises
+    ------
+    ValueError
+        If no parameters are defined (neither explicitly nor implicitly).
+    ValueError
+        If defined parameters are invalid python identifiers.
+
+    Examples
+    --------
+    Basic usage::
+
+        @deprecated_params(params="a, b, c")
+        def foo(**kwargs):
+            pass
+
+        foo(x=2, y=3, z=4)
+        # No warning
+
+        foo(a=2, b=3, z=4)
+        # WARNING  The parameters a and b of method foo have been deprecated and may be removed in a later version.
+
+    You can also specify additional information for a more precise warning::
+
+        @deprecated_params(
+            params="a, b, c",
+            since="v0.2",
+            until="v0.4",
+            message="The letters x, y, z are cooler."
+        )
+        def foo(**kwargs):
+            pass
+
+        foo(a=2)
+        # WARNING  The parameter a of method foo has been deprecated since v0.2 and is expected to be removed after v0.4. The letters x, y, z are cooler.
+
+    Basic parameter redirection::
+
+        @deprecated_params(redirections=[
+            # Two ways to redirect one parameter to another:
+            ("old_param", "new_param"),
+            lambda old_param2: {"new_param22": old_param2}
+        ])
+        def foo(**kwargs):
+            return kwargs
+
+        foo(x=1, old_param=2)
+        # WARNING  The parameter old_param of method foo has been deprecated and may be removed in a later version.
+        # returns {"x": 1, "new_param": 2}
+
+    Redirecting using a calculated value::
+
+        @deprecated_params(redirections=[
+            lambda runtime_in_ms: {"run_time": runtime_in_ms / 1000}
+        ])
+        def foo(**kwargs):
+            return kwargs
+
+        foo(runtime_in_ms=500)
+        # WARNING  The parameter runtime_in_ms of method foo has been deprecated and may be removed in a later version.
+        # returns {"run_time": 0.5}
+
+    Redirecting multiple parameter values to one::
+
+        @deprecated_params(redirections=[
+            lambda buff_x=1, buff_y=1: {"buff": (buff_x, buff_y)}
+        ])
+        def foo(**kwargs):
+            return kwargs
+
+        foo(buff_x=2)
+        # WARNING  The parameter buff_x of method foo has been deprecated and may be removed in a later version.
+        # returns {"buff": (2, 1)}
+
+    Redirect one parameter to multiple::
+
+        @deprecated_params(redirections=[
+            lambda buff=1: {"buff_x": buff[0], "buff_y": buff[1]} if isinstance(buff, tuple)
+                    else {"buff_x": buff,    "buff_y": buff}
+        ])
+        def foo(**kwargs):
+            return kwargs
+
+        foo(buff=0)
+        # WARNING  The parameter buff of method foo has been deprecated and may be removed in a later version.
+        # returns {"buff_x": 0, buff_y: 0}
+
+        foo(buff=(1,2))
+        # WARNING  The parameter buff of method foo has been deprecated and may be removed in a later version.
+        # returns {"buff_x": 1, buff_y: 2}
+
+
+    """
+    # Check if decorator is used without parenthesis
+    if callable(params):
+        raise ValueError("deprecate_parameters requires arguments to be specified.")
+
+    if params is None:
+        params = []
+
+    # Construct params list
+    params = re.split("[,\s]+", params) if isinstance(params, str) else list(params)
+
+    # Add params which are only implicitly given via redirections
+    if redirections is None:
+        redirections = []
+    for redirector in redirections:
+        if isinstance(redirector, tuple):
+            params.append(redirector[0])
+        else:
+            params.extend(inspect.getargspec(redirector).args)
+            print(inspect.getargspec(redirector).args)
+    # Keep ordering of params so that warning message is consistently the same
+    # This will also help pass unit testing
+    params = list(dict.fromkeys(params))
+
+    # Make sure params only contains valid identifiers
+    identifier = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE)
+    if not all(re.match(identifier, param) for param in params):
+        raise ValueError("Given parameter values are invalid.")
+
+    redirections = list(redirections)
+
+    def warning_msg(func: Callable, used: List[str]):
+        """Generate the deprecation warning message.
+
+        Parameters
+        ----------
+        func
+            The callable with deprecated parameters.
+        used
+            The list of deprecated parameters used in a call.
+
+        Returns
+        -------
+        str
+            The deprecation message.
+        """
+        what, name = _get_callable_info(func)
+        plural = len(used) > 1
+        parameter_s = "s" if plural else ""
+        used_ = ", ".join(used[:-1]) + " and " + used[-1] if plural else used[0]
+        has_have_been = "have been" if plural else "has been"
+        deprecated = _deprecation_text_component(since, until, message)
+        return f"The parameter{parameter_s} {used_} of {what} {name} {has_have_been} {deprecated}"
+
+    def redirect_params(kwargs: dict, used: List[str]):
+        """Adjust the keyword arguments as defined by the redirections.
+
+        Parameters
+        ----------
+        kwargs
+            The keyword argument dictionary to be updated.
+        used
+            The list of depecated parameters used in a call.
+        """
+        for redirector in redirections:
+            if isinstance(redirector, tuple):
+                old_param, new_param = redirector
+                if old_param in used:
+                    kwargs[new_param] = kwargs.pop(old_param)
+            else:
+                redirector_params = inspect.getargspec(redirector).args
+                redirector_args = {}
+                for redirector_param in redirector_params:
+                    if redirector_param in used:
+                        redirector_args[redirector_param] = kwargs.pop(redirector_param)
+                if len(redirector_args) > 0:
+                    kwargs.update(redirector(**redirector_args))
+
+    def deprecate_params(func, *args, **kwargs):
+        """The actual decorator function used to extend the callables behavior.
+
+        Logs a warning message when a deprecated parameter is used and redirects it if
+        specified.
+
+        Parameters
+        ----------
+        func
+            The callable to decorate.
+        args
+            The arguments passed to the given callable.
+        kwargs
+            The keyword arguments passed to the given callable.
+
+        Returns
+        -------
+        Any
+            The return value of the given callable when beeing passed the given
+            arguments.
+
+        """
+        used = []
+        for param in params:
+            if param in kwargs:
+                used.append(param)
+
+        if len(used) > 0:
+            logger.warning(warning_msg(func, used))
+            redirect_params(kwargs, used)
+        return func(*args, **kwargs)
+
+    return decorator(deprecate_params)
diff --git a/poetry.lock b/poetry.lock
index 81469af7ae..28b0dced37 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -173,14 +173,6 @@ packaging = "*"
 six = ">=1.9.0"
 webencodings = "*"
 
-[[package]]
-name = "certifi"
-version = "2020.12.5"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
-optional = false
-python-versions = "*"
-
 [[package]]
 name = "cffi"
 version = "1.14.5"
@@ -200,14 +192,6 @@ category = "dev"
 optional = false
 python-versions = ">=3.6.1"
 
-[[package]]
-name = "chardet"
-version = "4.0.0"
-description = "Universal encoding detector for Python 2 and 3"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
 [[package]]
 name = "click"
 version = "7.1.2"
@@ -229,7 +213,7 @@ click = "*"
 
 [[package]]
 name = "cloup"
-version = "0.7.0"
+version = "0.7.1"
 description = "Option groups and subcommand help sections for pallets/click"
 category = "main"
 optional = false
@@ -311,11 +295,11 @@ python-versions = ">=3.6, <3.7"
 
 [[package]]
 name = "decorator"
-version = "4.4.2"
+version = "5.0.7"
 description = "Decorators for Humans"
 category = "main"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+python-versions = ">=3.5"
 
 [[package]]
 name = "defusedxml"
@@ -360,7 +344,7 @@ python-versions = "*"
 
 [[package]]
 name = "docutils"
-version = "0.16"
+version = "0.17.1"
 description = "Docutils -- Python Documentation Utilities"
 category = "dev"
 optional = false
@@ -482,11 +466,11 @@ license = ["editdistance-s"]
 
 [[package]]
 name = "idna"
-version = "2.10"
+version = "3.1"
 description = "Internationalized Domain Names in Applications (IDNA)"
 category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+optional = true
+python-versions = ">=3.4"
 
 [[package]]
 name = "imagesize"
@@ -1029,14 +1013,14 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "networkx"
-version = "2.5.1"
+version = "2.5"
 description = "Python package for creating and manipulating graphs and networks"
 category = "main"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
-decorator = ">=4.3,<5"
+decorator = ">=4.3.0"
 
 [package.extras]
 all = ["numpy", "scipy", "pandas", "matplotlib", "pygraphviz", "pydot", "pyyaml", "lxml", "pytest"]
@@ -1504,20 +1488,14 @@ python-versions = "*"
 
 [[package]]
 name = "requests"
-version = "2.25.1"
+version = "2.15.1"
 description = "Python HTTP for Humans."
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<5"
-idna = ">=2.5,<3"
-urllib3 = ">=1.21.1,<1.27"
+python-versions = "*"
 
 [package.extras]
-security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
+security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"]
 socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
 [[package]]
@@ -1602,7 +1580,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "sphinx"
-version = "3.5.4"
+version = "3.5.3"
 description = "Python documentation generator"
 category = "dev"
 optional = false
@@ -1612,7 +1590,7 @@ python-versions = ">=3.5"
 alabaster = ">=0.7,<0.8"
 babel = ">=1.3"
 colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
-docutils = ">=0.12,<0.17"
+docutils = ">=0.12"
 imagesize = "*"
 Jinja2 = ">=2.3"
 packaging = "*"
@@ -1817,25 +1795,12 @@ python-versions = "*"
 
 [[package]]
 name = "typing-extensions"
-version = "3.7.4.3"
+version = "3.10.0.0"
 description = "Backported and Experimental Type Hints for Python 3.5+"
 category = "main"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "urllib3"
-version = "1.26.4"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-
-[package.extras]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-brotli = ["brotlipy (>=0.6.0)"]
-
 [[package]]
 name = "virtualenv"
 version = "20.4.4"
@@ -1910,7 +1875,8 @@ webgl_renderer = ["grpcio", "grpcio-tools"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6.2"
-content-hash = "6a5893c04232ce6f983ebd8abc432406f053bc86ef83e0d1be0898648d22b6df"
+content-hash = "01d0ffdb8331e48bb3251818b2a7e9f9e34d06be117ed5e6a4019b1f98f5b843"
+
 
 [metadata.files]
 alabaster = [
@@ -1985,10 +1951,6 @@ bleach = [
     {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"},
     {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"},
 ]
-certifi = [
-    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
-    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
-]
 cffi = [
     {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
     {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
@@ -2032,10 +1994,6 @@ cfgv = [
     {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
     {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
 ]
-chardet = [
-    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
-    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
-]
 click = [
     {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
     {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
@@ -2044,8 +2002,8 @@ click-default-group = [
     {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"},
 ]
 cloup = [
-    {file = "cloup-0.7.0-py2.py3-none-any.whl", hash = "sha256:7dc05918f12b0dee2d17220d4b96aaa835591b572d279a92971549968f23bfc8"},
-    {file = "cloup-0.7.0.tar.gz", hash = "sha256:412f8bc43b3c551ba7568835eb7ca1f0ce8fdfa164088f82008fb57e41e3d308"},
+    {file = "cloup-0.7.1-py2.py3-none-any.whl", hash = "sha256:947c881ada77ea2f7b2876f7b5bb2fc7e5406daac5a045342acf8de35bcce5d7"},
+    {file = "cloup-0.7.1.tar.gz", hash = "sha256:f26cc500dda4e4c9444ac1008b82376df8d72ecfa43a6310a9b3d54ef98add7b"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -2125,8 +2083,8 @@ dataclasses = [
     {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"},
 ]
 decorator = [
-    {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
-    {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
+    {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"},
+    {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"},
 ]
 defusedxml = [
     {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
@@ -2145,8 +2103,8 @@ distlib = [
     {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
 ]
 docutils = [
-    {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
-    {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
+    {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
+    {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
 ]
 entrypoints = [
     {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
@@ -2299,8 +2257,8 @@ identify = [
     {file = "identify-2.2.4.tar.gz", hash = "sha256:9bcc312d4e2fa96c7abebcdfb1119563b511b5e3985ac52f60d9116277865b2e"},
 ]
 idna = [
-    {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
-    {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
+    {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
+    {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"},
 ]
 imagesize = [
     {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
@@ -2529,20 +2487,39 @@ markupsafe = [
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
+    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
+    {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
     {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
 ]
 matplotlib = [
@@ -2639,8 +2616,8 @@ nest-asyncio = [
     {file = "nest_asyncio-1.5.1.tar.gz", hash = "sha256:afc5a1c515210a23c461932765691ad39e8eba6551c055ac8d5546e69250d0aa"},
 ]
 networkx = [
-    {file = "networkx-2.5.1-py3-none-any.whl", hash = "sha256:0635858ed7e989f4c574c2328380b452df892ae85084144c73d8cd819f0c4e06"},
-    {file = "networkx-2.5.1.tar.gz", hash = "sha256:109cd585cac41297f71103c3c42ac6ef7379f29788eb54cb751be5a663bb235a"},
+    {file = "networkx-2.5-py3-none-any.whl", hash = "sha256:8c5812e9f798d37c50570d15c4a69d5710a18d77bafc903ee9c5fba7454c616c"},
+    {file = "networkx-2.5.tar.gz", hash = "sha256:7978955423fbc9639c10498878be59caf99b44dc304c2286162fd24b458c1602"},
 ]
 nodeenv = [
     {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
@@ -2953,6 +2930,7 @@ pyzmq = [
     {file = "pyzmq-22.0.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b62ea18c0458a65ccd5be90f276f7a5a3f26a6dea0066d948ce2fa896051420f"},
     {file = "pyzmq-22.0.3-cp38-cp38-win32.whl", hash = "sha256:81e7df0da456206201e226491aa1fc449da85328bf33bbeec2c03bb3a9f18324"},
     {file = "pyzmq-22.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f52070871a0fd90a99130babf21f8af192304ec1e995bec2a9533efc21ea4452"},
+    {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:c5e29fe4678f97ce429f076a2a049a3d0b2660ada8f2c621e5dc9939426056dd"},
     {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d18ddc6741b51f3985978f2fda57ddcdae359662d7a6b395bc8ff2292fca14bd"},
     {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4231943514812dfb74f44eadcf85e8dd8cf302b4d0bce450ce1357cac88dbfdc"},
     {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:23a74de4b43c05c3044aeba0d1f3970def8f916151a712a3ac1e5cd9c0bc2902"},
@@ -3015,8 +2993,8 @@ regex = [
     {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"},
 ]
 requests = [
-    {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
-    {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
+    {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"},
+    {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"},
 ]
 rich = [
     {file = "rich-6.2.0-py3-none-any.whl", hash = "sha256:fa55d5d6ba9a0df1f1c95518891b57b13f1d45548a9a198a87b093fceee513ec"},
@@ -3074,8 +3052,8 @@ soupsieve = [
     {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"},
 ]
 sphinx = [
-    {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"},
-    {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"},
+    {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"},
+    {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"},
 ]
 sphinx-copybutton = [
     {file = "sphinx-copybutton-0.3.1.tar.gz", hash = "sha256:0e0461df394515284e3907e3f418a0c60ef6ab6c9a27a800c8552772d0a402a2"},
@@ -3209,13 +3187,9 @@ typed-ast = [
     {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
-    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
-    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
-]
-urllib3 = [
-    {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
-    {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
+    {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
+    {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
+    {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
 ]
 virtualenv = [
     {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"},
diff --git a/pyproject.toml b/pyproject.toml
index 9e061370ee..e5243d6c3f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ rich = "^6.0"
 pycairo = "^1.19"
 manimpango = "^0.2.4"
 networkx = "^2.5"
-decorator = "<5.0.0"  # remove as soon as https://github.com/networkx/networkx/issues/4718 is resolved
+decorator = "^5.0.7"
 setuptools = "*"
 importlib-metadata = {version = "*", python = "<3.8"}
 grpcio =  { version = "1.33.*", optional = true }
diff --git a/tests/test_composition.py b/tests/test_composition.py
index 900bc0ac29..0d804f1ec3 100644
--- a/tests/test_composition.py
+++ b/tests/test_composition.py
@@ -1,15 +1,15 @@
 from manim.animation.animation import Animation, Wait
 from manim.animation.composition import AnimationGroup, Succession
-from manim.animation.fading import FadeIn, FadeInFrom, FadeOutAndShift
-from manim.constants import DOWN
+from manim.animation.fading import FadeIn, FadeOut
+from manim.constants import DOWN, UP
 from manim.mobject.geometry import Circle, Line, Square
 
 
 def test_succession_timing():
     """Test timing of animations in a succession."""
     line = Line()
-    animation_1s = FadeInFrom(line, direction=DOWN, run_time=1.0)
-    animation_4s = FadeOutAndShift(line, direction=DOWN, run_time=4.0)
+    animation_1s = FadeIn(line, shift=UP, run_time=1.0)
+    animation_4s = FadeOut(line, shift=DOWN, run_time=4.0)
     succession = Succession(animation_1s, animation_4s)
     assert succession.get_run_time() == 5.0
     succession.begin()
@@ -33,13 +33,13 @@ def test_succession_timing():
 def test_succession_in_succession_timing():
     """Test timing of nested successions."""
     line = Line()
-    animation_1s = FadeInFrom(line, direction=DOWN, run_time=1.0)
-    animation_4s = FadeOutAndShift(line, direction=DOWN, run_time=4.0)
+    animation_1s = FadeIn(line, shift=UP, run_time=1.0)
+    animation_4s = FadeOut(line, shift=DOWN, run_time=4.0)
     nested_succession = Succession(animation_1s, animation_4s)
     succession = Succession(
-        FadeInFrom(line, direction=DOWN, run_time=4.0),
+        FadeIn(line, shift=UP, run_time=4.0),
         nested_succession,
-        FadeInFrom(line, direction=DOWN, run_time=1.0),
+        FadeIn(line, shift=UP, run_time=1.0),
     )
     assert nested_succession.get_run_time() == 5.0
     assert succession.get_run_time() == 10.0
diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py
new file mode 100644
index 0000000000..aed5163566
--- /dev/null
+++ b/tests/test_deprecation.py
@@ -0,0 +1,389 @@
+import warnings
+
+import pytest
+
+from manim import logger
+from manim.utils.deprecation import deprecated, deprecated_params
+
+
+@deprecated
+class Foo:
+    def __init__(self):
+        pass
+
+
+@deprecated(since="v0.6.0")
+class Bar:
+    """The Bar class."""
+
+    def __init__(self):
+        pass
+
+
+@deprecated(until="06/01/2021")
+class Baz:
+    """The Baz class."""
+
+    def __init__(self):
+        pass
+
+
+@deprecated(since="0.7.0", until="0.9.0-rc2")
+class Qux:
+    def __init__(self):
+        pass
+
+
+@deprecated(message="Use something else.")
+class Quux:
+    def __init__(self):
+        pass
+
+
+@deprecated(replacement="ReplaceQuuz")
+class Quuz:
+    def __init__(self):
+        pass
+
+
+class ReplaceQuuz:
+    def __init__(self):
+        pass
+
+
+@deprecated(
+    since="0.7.0",
+    until="1.2.1",
+    replacement="ReplaceQuuz",
+    message="Don't use this please.",
+)
+class QuuzAll:
+    def __init__(self):
+        pass
+
+
+doc_admonition = "\n\n.. admonition:: Deprecated\n  :class: attention\n\n  "
+
+
+def test_deprecate_class_no_args(caplog):
+    """Test the deprecation of a class (decorator with no arguments)."""
+    f = Foo()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Foo has been deprecated and may be removed in a later version."
+    )
+    assert f.__doc__ == f"{doc_admonition}{msg}"
+
+
+def test_deprecate_class_since(caplog):
+    """Test the deprecation of a class (decorator with since argument)."""
+    b = Bar()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Bar has been deprecated since v0.6.0 and may be removed in a later version."
+    )
+    assert b.__doc__ == f"The Bar class.{doc_admonition}{msg}"
+
+
+def test_deprecate_class_until(caplog):
+    """Test the deprecation of a class (decorator with until argument)."""
+    bz = Baz()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Baz has been deprecated and is expected to be removed after 06/01/2021."
+    )
+    assert bz.__doc__ == f"The Baz class.{doc_admonition}{msg}"
+
+
+def test_deprecate_class_since_and_until(caplog):
+    """Test the deprecation of a class (decorator with since and until arguments)."""
+    qx = Qux()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Qux has been deprecated since 0.7.0 and is expected to be removed after 0.9.0-rc2."
+    )
+    assert qx.__doc__ == f"{doc_admonition}{msg}"
+
+
+def test_deprecate_class_msg(caplog):
+    """Test the deprecation of a class (decorator with msg argument)."""
+    qu = Quux()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Quux has been deprecated and may be removed in a later version. Use something else."
+    )
+    assert qu.__doc__ == f"{doc_admonition}{msg}"
+
+
+def test_deprecate_class_replacement(caplog):
+    """Test the deprecation of a class (decorator with replacement argument)."""
+    qz = Quuz()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Quuz has been deprecated and may be removed in a later version. Use ReplaceQuuz instead."
+    )
+    doc_msg = "The class Quuz has been deprecated and may be removed in a later version. Use :class:`~.ReplaceQuuz` instead."
+    assert qz.__doc__ == f"{doc_admonition}{doc_msg}"
+
+
+def test_deprecate_class_all(caplog):
+    """Test the deprecation of a class (decorator with all arguments)."""
+    qza = QuuzAll()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class QuuzAll has been deprecated since 0.7.0 and is expected to be removed after 1.2.1. Use ReplaceQuuz instead. Don't use this please."
+    )
+    doc_msg = "The class QuuzAll has been deprecated since 0.7.0 and is expected to be removed after 1.2.1. Use :class:`~.ReplaceQuuz` instead. Don't use this please."
+    assert qza.__doc__ == f"{doc_admonition}{doc_msg}"
+
+
+def _get_caplog_record_msg(caplog):
+    logger_name, level, message = caplog.record_tuples[0]
+    return message
+
+
+@deprecated
+def useless(**kwargs):
+    pass
+
+
+class Top:
+    def __init__(self):
+        pass
+
+    @deprecated(since="0.8.0", message="This method is useless.")
+    def mid_func(self):
+        """Middle function in Top."""
+
+        pass
+
+    @deprecated(until="1.4.0", replacement="Top.NewNested")
+    class Nested:
+        def __init__(self):
+            pass
+
+    class NewNested:
+        def __init__(self):
+            pass
+
+        @deprecated(since="1.0.0", until="12/25/2025")
+        def nested_func(self):
+            """Nested function in Top.NewNested."""
+
+            pass
+
+    class Bottom:
+        def __init__(self):
+            pass
+
+        def normal_func(self):
+            @deprecated
+            def nested_func(self):
+                pass
+
+            return nested_func
+
+    @deprecated_params(params="a, b, c", message="Use something else.")
+    def foo(self, **kwargs):
+        pass
+
+    @deprecated_params(params="a", since="v0.2", until="v0.4")
+    def bar(self, **kwargs):
+        pass
+
+    @deprecated_params(redirections=[("old_param", "new_param")])
+    def baz(self, **kwargs):
+        return kwargs
+
+    @deprecated_params(
+        redirections=[lambda runtime_in_ms: {"run_time": runtime_in_ms / 1000}]
+    )
+    def qux(self, **kwargs):
+        return kwargs
+
+    @deprecated_params(
+        redirections=[
+            lambda point2D_x=1, point2D_y=1: {"point2D": (point2D_x, point2D_y)}
+        ]
+    )
+    def quux(self, **kwargs):
+        return kwargs
+
+    @deprecated_params(
+        redirections=[
+            lambda point2D=1: {"x": point2D[0], "y": point2D[1]}
+            if isinstance(point2D, tuple)
+            else {"x": point2D, "y": point2D}
+        ]
+    )
+    def quuz(self, **kwargs):
+        return kwargs
+
+
+def test_deprecate_func_no_args(caplog):
+    """Test the deprecation of a method (decorator with no arguments)."""
+    useless()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The method useless has been deprecated and may be removed in a later version."
+    )
+    assert useless.__doc__ == f"{doc_admonition}{msg}"
+
+
+def test_deprecate_func_in_class_since_and_message(caplog):
+    """Test the deprecation of a method within a class (decorator with since and message arguments)."""
+    t = Top()
+    t.mid_func()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The method Top.mid_func has been deprecated since 0.8.0 and may be removed in a later version. This method is useless."
+    )
+    assert t.mid_func.__doc__ == f"Middle function in Top.{doc_admonition}{msg}"
+
+
+def test_deprecate_nested_class_until_and_replacement(caplog):
+    """Test the deprecation of a nested class (decorator with until and replacement arguments)."""
+    n = Top().Nested()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The class Top.Nested has been deprecated and is expected to be removed after 1.4.0. Use Top.NewNested instead."
+    )
+    doc_msg = "The class Top.Nested has been deprecated and is expected to be removed after 1.4.0. Use :class:`~.Top.NewNested` instead."
+    assert n.__doc__ == f"{doc_admonition}{doc_msg}"
+
+
+def test_deprecate_nested_class_func_since_and_until(caplog):
+    """Test the deprecation of a method within a nested class (decorator with since and until arguments)."""
+    n = Top().NewNested()
+    n.nested_func()
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The method Top.NewNested.nested_func has been deprecated since 1.0.0 and is expected to be removed after 12/25/2025."
+    )
+    assert (
+        n.nested_func.__doc__
+        == f"Nested function in Top.NewNested.{doc_admonition}{msg}"
+    )
+
+
+def test_deprecate_nested_func(caplog):
+    """Test the deprecation of a nested method (decorator with no arguments)."""
+    b = Top().Bottom()
+    ans = b.normal_func()
+    ans(1)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The method Top.Bottom.normal_func.<locals>.nested_func has been deprecated and may be removed in a later version."
+    )
+    assert ans.__doc__ == f"{doc_admonition}{msg}"
+
+
+def test_deprecate_func_params(caplog):
+    """Test the deprecation of method parameters (decorator with params argument)."""
+    t = Top()
+    t.foo(a=2, b=3, z=4)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameters a and b of method Top.foo have been deprecated and may be removed in a later version. Use something else."
+    )
+
+
+def test_deprecate_func_single_param_since_and_until(caplog):
+    """Test the deprecation of a single method parameter (decorator with since and until arguments)."""
+    t = Top()
+    t.bar(a=1, b=2)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameter a of method Top.bar has been deprecated since v0.2 and is expected to be removed after v0.4."
+    )
+
+
+def test_deprecate_func_param_redirect_tuple(caplog):
+    """Test the deprecation of a method parameter and redirecting it to a new one using tuple."""
+    t = Top()
+    obj = t.baz(x=1, old_param=2)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameter old_param of method Top.baz has been deprecated and may be removed in a later version."
+    )
+    assert obj == {"x": 1, "new_param": 2}
+
+
+def test_deprecate_func_param_redirect_lambda(caplog):
+    """Test the deprecation of a method parameter and redirecting it to a new one using lambda function."""
+    t = Top()
+    obj = t.qux(runtime_in_ms=500)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameter runtime_in_ms of method Top.qux has been deprecated and may be removed in a later version."
+    )
+    assert obj == {"run_time": 0.5}
+
+
+def test_deprecate_func_param_redirect_many_to_one(caplog):
+    """Test the deprecation of multiple method parameters and redirecting them to one."""
+    t = Top()
+    obj = t.quux(point2D_x=3, point2D_y=5)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameters point2D_x and point2D_y of method Top.quux have been deprecated and may be removed in a later version."
+    )
+    assert obj == {"point2D": (3, 5)}
+
+
+def test_deprecate_func_param_redirect_one_to_many(caplog):
+    """Test the deprecation of one method parameter and redirecting it to many."""
+    t = Top()
+    obj1 = t.quuz(point2D=0)
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameter point2D of method Top.quuz has been deprecated and may be removed in a later version."
+    )
+    assert obj1 == {"x": 0, "y": 0}
+
+    caplog.clear()
+
+    obj2 = t.quuz(point2D=(2, 3))
+    assert len(caplog.record_tuples) == 1
+    msg = _get_caplog_record_msg(caplog)
+    assert (
+        msg
+        == "The parameter point2D of method Top.quuz has been deprecated and may be removed in a later version."
+    )
+    assert obj2 == {"x": 2, "y": 3}
diff --git a/tests/test_graphical_units/test_creation.py b/tests/test_graphical_units/test_creation.py
index eacfe6bc12..75d74073a9 100644
--- a/tests/test_graphical_units/test_creation.py
+++ b/tests/test_graphical_units/test_creation.py
@@ -41,24 +41,6 @@ def construct(self):
         self.play(FadeIn(square))
 
 
-class FadeInFromTest(Scene):
-    def construct(self):
-        square = Square()
-        self.play(FadeInFrom(square, direction=UP))
-
-
-class FadeOutAndShiftTest(Scene):
-    def construct(self):
-        square = Square()
-        self.play(FadeOutAndShift(square, direction=UP))
-
-
-class FadeInFromLargeTest(Scene):
-    def construct(self):
-        square = Square()
-        self.play(FadeInFromLarge(square))
-
-
 class GrowFromPointTest(Scene):
     def construct(self):
         square = Square()


Code review comments:
diff_hunk: @@ -41,7 +41,7 @@ def construct(self):
         self.add(grid, grid_title)  # Make sure title is on top of grid
         self.play(
             FadeOut(title),
-            FadeInFrom(grid_title, direction=DOWN),
+            FadeIn(grid_title, direction=UP),
path: example_scenes/basic.py
text: @user1:
```suggestion
            FadeIn(grid_title, shift=UP),
```