-1

I find surrounding these lines of code not accurate.

from manim import *

class CodeTrackingAnimation(Scene):
    def construct(self):
        code_str = '''#include<iostream>
        using namespace std;
        int main(){
            int sum = 0;
            for(int i=0;i<n;i++){
                sum += i;
                }
                return 0;
                }'''
        code = self.build_code_block(code_str)
        for i in range(len(code.code)-1):
            self.highlight(i, i+1)
    
    def build_code_block(self, code_str):
        # build the code block
        code = Code(code=code_str, language='C++', background="window")
        self.add(code)
        # build sliding windows (SurroundingRectangle)
        self.sliding_wins = VGroup()
        height = code.code[0].height
        for line in code.code:
            self.sliding_wins.add(SurroundingRectangle(line).set_fill(YELLOW).set_opacity(0))

        self.add(self.sliding_wins)
        return code

    
    def highlight(self, prev_line, line):
        self.play(self.sliding_wins[prev_line].animate.set_opacity(0.3))
        self.play(ReplacementTransform(self.sliding_wins[prev_line], self.sliding_wins[line]))
        self.play(self.sliding_wins[line].animate.set_opacity(0.3))

Above is my code. I want the highlight (SurroundingRectangle precisely surround the codeline), but the SurroundingRectangle is out of the codeline's boundary (However, the first codeline's SurroundingRectangle is in the right position).

enter image description here

enter image description here

What's the problem with my code? How can I accurately highlight these codelines without using coordinates parameters?

2 Answers2

1

Initial tips

Before addressing the issue you asked for, some tricks:

  1. When using triple quotes to write the code as a string, the leading spaces inside the triple quotes are preserved, and this is why you get the code badly indented, like in this image:

    Bad

    This can be fixed if the first line of your code begins in the line below the triple quote opening, i.e:

        def construct(self):
            code_str = '''
            #include<iostream>
            using namespace std;
            int main(){
                int sum = 0;
                for(int i=0;i<n;i++){
                    sum += i;
                }
                return 0;
            }'''
    

    Manim removes the spaces caused by the indentation of the triple-quoted string, but only if all lines have the same number of leading spaces. This mechanism did not work in your original version of the code because the first line had no spaces on the left.

    Using this trick, the Code now looks like this:

    Good

  2. This is a matter of taste but... The line spacing used by default is too tight imho. And that causes overlapping of the highlighting boxes to the lines above and below. It can be increased by passing line_spacing=1 to the Code() initializer.

    The result is now:

    enter image description here

Now, the issue

All of the above is unrelated to the real issue here. The bounding boxes of each code elements are wrong.

This is due to the fact that the code.code object not only includes the text that can be read in the video. It also includes some "invisible Dot() objects". I don't know what is the purpose of these objects, since they can be safely removed and do not affect the output. But the presence of those invisible objects messes up the bounding boxes of the rectangles.

Removing them is as easy as:

        code = Code(code=code_str, language='C++', background="window",
                    line_spacing=1)
        code.code = remove_invisible_chars(code.code)  # <---- HERE
        self.add(code)

After this, the higlihgts come right:

Result

A final trick

It is possible to make the higlight rectangle to fill the whole line being highlighted, instead of simply the part which has characters in that line. This may be preferable, depending on your taste. This is the result I'm talking about:

Full line

To achieve this effect, you have to stretch the rectangle and to align it to the code background. In the case of your code, this means to change your lines:

        for line in code.code:
            self.sliding_wins.add(SurroundingRectangle(line).set_fill(YELLOW).set_opacity(0))

to

        for line in code.code:
            self.sliding_wins.add(
                SurroundingRectangle(line)
                .set_fill(YELLOW)
                .set_opacity(0)
                .stretch_to_fit_width(code.background_mobject.get_width())
                .align_to(code.background_mobject, LEFT)
            )
JLDiaz
  • 1,248
  • 9
  • 21
0

with the intention of supporting JLDiaz's response (aimed at helping people who are beginners like me in using this library), when I added the changes that he suggested to obtain the desired animation, in my case, I encountered a name error, specifically, NameError: name 'remove_invisible_chars' is not defined (I found documentation for this function here: https://docs.manim.community/en/stable/reference/manim.mobject.text.text_mobject.html).

To fix it, I went directly to the manim documentation, which would be: https://docs.manim.community/en/stable/_modules/manim/mobject/text/text_mobject.html and added the following local function inside the build_code_block(self, code_str) method: remove_invisible_chars(mobject: SVGMobject) -> SVGMobject. Obtaining the following code that gives the expected result:

from manim import *

class CodeTrackingAnimation(Scene):
    def construct(self):
        code_str = '''
        #include<iostream>
        using namespace std;
        int main(){
            int sum = 0;
            for(int i=0;i<n;i++){
                sum += i;
            }
            return 0;
        }'''
        code = self.build_code_block(code_str)
        for i in range(len(code.code)-1):
            self.highlight(i, i+1)
    
    def build_code_block(self, code_str):
        def remove_invisible_chars(mobject: SVGMobject) -> SVGMobject:
            """Function to remove unwanted invisible characters from some mobjects.

            Parameters
            ----------
            mobject
                Any SVGMobject from which we want to remove unwanted invisible characters.

            Returns
            -------
            :class:`~.SVGMobject`
                The SVGMobject without unwanted invisible characters.
            """
            # TODO: Refactor needed
            iscode = False
            if mobject.__class__.__name__ == "Text":
                mobject = mobject[:]
            elif mobject.__class__.__name__ == "Code":
                iscode = True
                code = mobject
                mobject = mobject.code
            mobject_without_dots = VGroup()
            if mobject[0].__class__ == VGroup:
                for i in range(len(mobject)):
                    mobject_without_dots.add(VGroup())
                    mobject_without_dots[i].add(*(k for k in mobject[i] if k.__class__ != Dot))
            else:
                mobject_without_dots.add(*(k for k in mobject if k.__class__ != Dot))
            if iscode:
                code.code = mobject_without_dots
                return code
            return mobject_without_dots

        # build the code block
        code = Code(code=code_str, language='C++', background="window", line_spacing=1)
        code.code = remove_invisible_chars(code.code)  # <---- HERE
        self.add(code)
        # build sliding windows (SurroundingRectangle)
        self.sliding_wins = VGroup()
        height = code.code[0].height
        for line in code.code:
            self.sliding_wins.add(
                SurroundingRectangle(line)
                .set_fill(YELLOW)
                .set_opacity(0)
                .stretch_to_fit_width(code.background_mobject.get_width())
                .align_to(code.background_mobject, LEFT)
            )

        self.add(self.sliding_wins)
        return code

    
    def highlight(self, prev_line, line):
        self.play(self.sliding_wins[prev_line].animate.set_opacity(0.3))
        self.play(ReplacementTransform(self.sliding_wins[prev_line], self.sliding_wins[line]))
        self.play(self.sliding_wins[line].animate.set_opacity(0.3))

Greetings and happy coding.