From 3a3f1f632e5b3c061afbd52c851ce88d2d649fce Mon Sep 17 00:00:00 2001 From: Luca Moschella Date: Sun, 26 Mar 2023 11:06:56 +0200 Subject: [PATCH] Refactor bullet lists with indent and group capabilities --- src/powermanim/layouts/arrangedbulletlist.py | 26 ---- src/powermanim/layouts/arrangedbullets.py | 124 ++++++++++++++++++ .../showcase/layouts/arrangedbulletlist.py | 34 ----- .../showcase/layouts/arrangedbullets.py | 33 +++++ .../showcase/templates/bulletlist.py | 18 ++- src/powermanim/templates/bulletlist.py | 21 ++- 6 files changed, 183 insertions(+), 73 deletions(-) delete mode 100644 src/powermanim/layouts/arrangedbulletlist.py create mode 100644 src/powermanim/layouts/arrangedbullets.py delete mode 100644 src/powermanim/showcase/layouts/arrangedbulletlist.py create mode 100644 src/powermanim/showcase/layouts/arrangedbullets.py diff --git a/src/powermanim/layouts/arrangedbulletlist.py b/src/powermanim/layouts/arrangedbulletlist.py deleted file mode 100644 index 1690f0a..0000000 --- a/src/powermanim/layouts/arrangedbulletlist.py +++ /dev/null @@ -1,26 +0,0 @@ -import typing as T - -from manim import * - - -class ArrangedBulletList(VGroup): - def __init__( - self, - *rows: T.Union[Tex, Text], - line_spacing: float = MED_LARGE_BUFF * 1.5, - left_buff: float = MED_LARGE_BUFF * 1.5, - shift: float = 0.0, - ): - """A VGroup that arranges the rows in a list of bullet points. - - Args: - rows: A list of items to be displayed. - line_spacing: The spacing between the rows. - left_buff: The spacing between the left edge of the rows and the left edge of the screen. - shift: The shift of the rows. - """ - g_rows = ( - VGroup(*rows).arrange(DOWN, aligned_edge=LEFT, buff=line_spacing).to_edge(LEFT, buff=left_buff).shift(shift) - ) - - super().__init__(*g_rows) diff --git a/src/powermanim/layouts/arrangedbullets.py b/src/powermanim/layouts/arrangedbullets.py new file mode 100644 index 0000000..055da41 --- /dev/null +++ b/src/powermanim/layouts/arrangedbullets.py @@ -0,0 +1,124 @@ +import typing as T +from collections import defaultdict + +from manim import * + + +class Bullet(VGroup): + def __init__( + self, + *text: T.Union[Tex, Text, MathTex], + level: int = 0, + group: T.Optional[int] = None, + adjustment: float = 0.0, + symbol: T.Optional[str] = r"$\bullet$", + buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, + text_components_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, + ): + """A class to represent a bullet point. + + Args: + text: The text to be displayed. + level: The indent level of the bullet point. + group: The group the bullet point belongs to, controls the animations. + adjustment: The adjustment of the bullet. + symbol: The symbol to be displayed as the bullet. + buff: The spacing between the bullet and the text. + text_components_buff: The spacing between the text components. + """ + self.level = level + self.adjustment = adjustment + self.group = group + + self.text = VGroup(*text).arrange(RIGHT, buff=text_components_buff) + self.bullet = symbol + + if symbol is not None: + self.bullet = Tex(symbol) + self.bullet.next_to(self.text, LEFT, buff=buff) + super().__init__(VGroup(self.bullet, self.text) if self.bullet is not None else self.text) + + def indent(self, indent_buff: float = MED_LARGE_BUFF * 1.5): + """Indent the bullet point. + + Args: + indent_buff: The spacing between the bullet and the text. + """ + self.shift(RIGHT * indent_buff * self.level) + + def unindent(self, indent_buff: float = MED_LARGE_BUFF * 1.5): + """Unindent the bullet point. + + Args: + indent_buff: The spacing between the bullet and the text. + """ + self.shift(LEFT * indent_buff * self.intend_level) + + def adjust(self, adjustment: T.Optional[float] = None): + """Adjust the bullet point. + + Args: + adjustment: The shift of the bullet. + """ + if adjustment is None: + adjustment = self.adjustment + self.shift(self.adjustment) + + +class ArrangedBullets(VGroup): + def __init__( + self, + *rows: T.Union[Bullet, MathTex, Tex, Text], + line_spacing: float = MED_LARGE_BUFF * 1.5, + indent_buff: float = MED_LARGE_BUFF * 1.5, + left_buff: float = MED_LARGE_BUFF * 1.5, + global_shift: float = 0.0, + ): + """A VGroup that arranges the rows in a list of bullet points. + + Args: + rows: A list of items to be displayed. + line_spacing: The spacing between the rows. + indent_buff: The spacing between the bullet and the text. + left_buff: The spacing between the left edge of the rows and the left edge of the screen. + global_shift: The global_shift of the rows. + """ + self.line_spacing = line_spacing + self.indent_buff = indent_buff + self.left_buff = left_buff + self.global_shift = global_shift + + rows = [(row if isinstance(row, Bullet) else Bullet(row)) for row in rows] + bullet_rows: T.Iterable[Bullet] = ( + VGroup(*rows) + .arrange(DOWN, aligned_edge=LEFT, buff=line_spacing) + .to_edge(LEFT, buff=left_buff) + .shift(global_shift) + ) + + for row in bullet_rows: + row.indent(indent_buff=indent_buff) + row.adjust() + + groups = [row.group for row in bullet_rows] + + # If there is a None and aso something else + if (None in groups) and len(set(groups)) != 1: + raise ValueError("The groups must be specified for all or no bullets at all.") + + if None in groups: + groups = list(range(len(bullet_rows))) + + group2bullet = defaultdict(list) + for i, row in enumerate(bullet_rows): + group = row.group + if group is None: + group = i + group2bullet[group].append(row) + + group_rows = [] + for _, bullets in group2bullet.items(): + group_rows.append(VGroup(*bullets)) + + super().__init__(*group_rows) + self.ngroups = len(self) diff --git a/src/powermanim/showcase/layouts/arrangedbulletlist.py b/src/powermanim/showcase/layouts/arrangedbulletlist.py deleted file mode 100644 index 7af628d..0000000 --- a/src/powermanim/showcase/layouts/arrangedbulletlist.py +++ /dev/null @@ -1,34 +0,0 @@ -from manim import * - -from powermanim.layouts.arrangedbulletlist import ArrangedBulletList -from powermanim.showcase.showcasescene import ShowcaseScene - - -class ArrangedBulletListShowcase(ShowcaseScene): - def showcasing(): - return ArrangedBulletList - - def construct(self): - rows = [ - Text(row) - for row in [ - "First row", - "Second row", - "Third row", - "Fourth row", - ] - ] - g_rows = VGroup(*rows).set_opacity(0.25) - g_rows.target = ArrangedBulletList( - *g_rows.copy(), - line_spacing=MED_LARGE_BUFF * 1.25, - left_buff=MED_LARGE_BUFF * 3, - ).set_opacity(1) - - self.play( - MoveToTarget(g_rows), - rate_func=there_and_back_with_pause, - run_time=3, - ) - - self.wait() diff --git a/src/powermanim/showcase/layouts/arrangedbullets.py b/src/powermanim/showcase/layouts/arrangedbullets.py new file mode 100644 index 0000000..38ae68b --- /dev/null +++ b/src/powermanim/showcase/layouts/arrangedbullets.py @@ -0,0 +1,33 @@ +from manim import * + +from powermanim.layouts.arrangedbullets import ArrangedBullets, Bullet +from powermanim.showcase.showcasescene import ShowcaseScene + + +class ArrangedBulletsShowcase(ShowcaseScene): + def showcasing(): + return ArrangedBullets + + def construct(self): + rows = [ + Bullet(Text("First row")), + Bullet(Text("Second row")), + Bullet(Text("Elements:")), + Bullet(Text("First element"), level=1, symbol="(1)"), + Bullet(Text("Second element"), level=1, symbol="(2)"), + Bullet(Text("Third element"), level=1, symbol="(3)"), + ] + g_rows = VGroup(*rows).set_opacity(0.25).scale(0.9) + g_rows.target = ArrangedBullets( + *g_rows.copy(), + line_spacing=MED_LARGE_BUFF * 1.25, + left_buff=MED_LARGE_BUFF * 3, + ).set_opacity(1) + + self.play( + MoveToTarget(g_rows), + rate_func=there_and_back_with_pause, + run_time=3, + ) + + self.wait() diff --git a/src/powermanim/showcase/templates/bulletlist.py b/src/powermanim/showcase/templates/bulletlist.py index 8fb950a..1d125cb 100644 --- a/src/powermanim/showcase/templates/bulletlist.py +++ b/src/powermanim/showcase/templates/bulletlist.py @@ -1,5 +1,6 @@ from manim import * +from powermanim.layouts.arrangedbullets import Bullet from powermanim.showcase.showcasescene import ShowcaseScene from powermanim.templates.bulletlist import BulletList @@ -10,13 +11,12 @@ def showcasing(): def construct(self): rows = [ - Text(row) - for row in [ - "First row", - "Second row", - "Third row", - "Fourth row", - ] + Bullet(Text("First row"), group=0), + Bullet(Text("Second row"), group=1), + Bullet(Text("Elements:"), group=2), + Bullet(Text("First element"), level=1, symbol="(1)", group=3), + Bullet(Text("Second element"), level=1, symbol="(2)", group=4), + Bullet(Text("Third element"), level=1, symbol="(3)", group=5), ] VGroup(*rows).set_opacity(0.5).scale(0.8) @@ -30,10 +30,14 @@ def construct(self): bullets.also_next(self) bullets.also_next(self) bullets.also_next(self) + bullets.also_next(self) + bullets.also_next(self) bullets.clear(self) bullets.only_next(self) bullets.only_next(self) bullets.only_next(self) bullets.only_next(self) + bullets.only_next(self) + bullets.only_next(self) bullets.clear(self) self.wait() diff --git a/src/powermanim/templates/bulletlist.py b/src/powermanim/templates/bulletlist.py index 228efab..3ebbf5c 100644 --- a/src/powermanim/templates/bulletlist.py +++ b/src/powermanim/templates/bulletlist.py @@ -3,7 +3,7 @@ from manim import * from powermanim.components.vgrouphighlight import VGroupHighlight -from powermanim.layouts.arrangedbulletlist import ArrangedBulletList +from powermanim.layouts.arrangedbullets import ArrangedBullets class BulletList(VGroup): @@ -11,8 +11,9 @@ def __init__( self, *rows: T.Union[Tex, Text], line_spacing: float = MED_LARGE_BUFF * 1.5, + indent_buff: float = MED_LARGE_BUFF * 1.5, left_buff: float = MED_LARGE_BUFF * 2, - shift: float = 0.0, + global_shift: float = 0.0, inactive_opacity: float = 0.5, active_opacity: float = 1.0, scale_active: float = 1.0, @@ -22,21 +23,23 @@ def __init__( Args: rows: A list of items to be displayed. line_spacing: The spacing between the rows. + indent_buff: The spacing between the bullet and the text. left_buff: The spacing between the left edge of the rows and the left edge of the screen. - shift: The shift to apply to the rows. + global_shift: The global_shift to apply to the rows. inactive_opacity: The opacity of the inactive items. active_opacity: The opacity of the active items. scale_active: The scale of the active items. """ - ArrangedBulletList( + self.arranged_list = ArrangedBullets( *rows, line_spacing=line_spacing, + indent_buff=indent_buff, left_buff=left_buff, - shift=shift, + global_shift=global_shift, ).set_opacity(inactive_opacity) self.rows = VGroupHighlight( - *rows, + *self.arranged_list, active_opacity=active_opacity, scale_active=scale_active, scale_about_edge=LEFT, @@ -49,10 +52,16 @@ def also_next(self, scene: Scene) -> None: """Highlights also the next item in the list.""" self.highlighted += 1 + if self.highlighted > self.arranged_list.ngroups: + raise StopIteration("No more elements to highlight.") + self.rows.highlight(scene=scene, indices=list(range(self.highlighted))) def only_next(self, scene: Scene) -> None: """Highlights only the next item in the list.""" + if self.highlighted > self.arranged_list.ngroups: + raise StopIteration("No more elements to highlight.") + self.rows.highlight(scene=scene, indices=self.highlighted) self.highlighted += 1