def construct(self): eq1_text = "4 x + 3 y = 0".split() eq2_text = "5 x - 2 y = 3".split() eq1_mob = TexMobject(*eq1_text) eq2_mob = TexMobject(*eq2_text) color_map = {"x": RED_B, "y": GREEN_C} eq1_mob.set_color_by_tex_to_color_map(color_map) eq2_mob.set_color_by_tex_to_color_map(color_map) for eq1_item, eq2_item in zip(eq1_mob, eq2_mob): eq2_item.align_to(eq1_item, LEFT) eq1 = VGroup(*eq1_mob) eq2 = VGroup(*eq2_mob) eq2.shift(DOWN) eq_group = VGroup(eq1, eq2) braces = Brace(eq_group, LEFT) eq_text = braces.get_text("A pair of equations") self.play(Write(eq_text)) self.play(GrowFromCenter(braces)) self.play(Write(eq1), Write(eq2)) self.wait(2)
def __init__(self, file_name=None, **kwargs): Container.__init__(self, **kwargs) self.file_name = file_name or self.file_name self.ensure_valid_file() self.style = self.style.lower() self.gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12:strati + 19] self.gen_code_json() self.code = self.gen_colored_lines() if self.insert_line_no: self.line_numbers = self.gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code self.background_mobject = SurroundingRectangle(forground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=0, fill_opacity=1, ) self.background_mobject.round_corners(self.corner_radius) else: if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code height = forground.get_height() + 0.1 * 3 + 2 * self.margin width = forground.get_width() + 0.1 * 3 + 2 * self.margin rrect = RoundedRectangle(corner_radius=self.corner_radius, height=height, width=width, stroke_width=0, color=self.background_color, fill_opacity=1) button_radius = 0.09 red_button = Dot(radius=button_radius, stroke_width=0, color='#ff5f56') red_button.shift(LEFT * button_radius * 3) yellow_button = Dot(radius=button_radius, stroke_width=0, color='#ffbd2e') green_button = Dot(radius=button_radius, stroke_width=0, color='#27c93f') green_button.shift(RIGHT * button_radius * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05)) self.background_mobject = VGroup(rrect, buttons) x = (height - forground.get_height()) / 2 - 0.1 * 3 self.background_mobject.shift(forground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: VGroup.__init__(self, self.background_mobject, self.line_numbers, *self.code, **kwargs) else: VGroup.__init__(self, self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), *self.code, **kwargs) self.move_to(np.array([0, 0, 0]))
def __init__(self, **kwargs): super().__init__(**kwargs) body = Cube(side_length=1) for dim, scale_factor in enumerate(self.body_dimensions): body.stretch(scale_factor, dim=dim) body.set_width(self.width) body.set_fill(self.shaded_body_color, opacity=1) body.sort(lambda p: p[2]) body[-1].set_fill(self.body_color) screen_plate = body.copy() keyboard = VGroup(*[ VGroup(*[ Square(**self.key_color_kwargs) for x in range(12 - y % 2) ]).arrange(RIGHT, buff=SMALL_BUFF) for y in range(4) ]).arrange(DOWN, buff=MED_SMALL_BUFF) keyboard.stretch_to_fit_width( self.keyboard_width_to_body_width * body.get_width(), ) keyboard.stretch_to_fit_height( self.keyboard_height_to_body_height * body.get_height(), ) keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF) keyboard.shift(MED_SMALL_BUFF * UP) body.add(keyboard) screen_plate.stretch(self.screen_thickness / self.body_dimensions[2], dim=2) screen = Rectangle( stroke_width=0, fill_color=BLACK, fill_opacity=1, ) screen.replace(screen_plate, stretch=True) screen.scale_in_place(self.screen_width_to_screen_plate_width) screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen_plate.add(screen) screen_plate.next_to(body, UP, buff=0) screen_plate.rotate( self.open_angle, RIGHT, about_point=screen_plate.get_bottom() ) self.screen_plate = screen_plate self.screen = screen axis = Line( body.get_corner(UP + LEFT + OUT), body.get_corner(UP + RIGHT + OUT), color=BLACK, stroke_width=2 ) self.axis = axis self.add(body, screen_plate, axis) self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN) self.rotate(np.pi / 6, DOWN, about_point=ORIGIN)
def __init__(self, **kwargs): if not hasattr(self, "args"): self.args = serialize_args([]) if not hasattr(self, "config"): self.config = serialize_config({ **kwargs, }) super().__init__(**kwargs) body = Cube(side_length=1) for dim, scale_factor in enumerate(self.body_dimensions): body.stretch(scale_factor, dim=dim) body.set_width(self.width) body.set_fill(self.shaded_body_color, opacity=1) body.sort(lambda p: p[2]) body[-1].set_fill(self.body_color) screen_plate = body.copy() keyboard = VGroup(*[ VGroup( *[Square(**self.key_color_kwargs) for x in range(12 - y % 2)]).arrange(RIGHT, buff=SMALL_BUFF) for y in range(4) ]).arrange(DOWN, buff=MED_SMALL_BUFF) keyboard.stretch_to_fit_width( self.keyboard_width_to_body_width * body.get_width(), ) keyboard.stretch_to_fit_height( self.keyboard_height_to_body_height * body.get_height(), ) keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF) keyboard.shift(MED_SMALL_BUFF * UP) body.add(keyboard) screen_plate.stretch(self.screen_thickness / self.body_dimensions[2], dim=2) screen = Rectangle( stroke_width=0, fill_color=BLACK, fill_opacity=1, ) screen.replace(screen_plate, stretch=True) screen.scale_in_place(self.screen_width_to_screen_plate_width) screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen_plate.add(screen) screen_plate.next_to(body, UP, buff=0) screen_plate.rotate(self.open_angle, RIGHT, about_point=screen_plate.get_bottom()) self.screen_plate = screen_plate self.screen = screen axis = Line(body.get_corner(UP + LEFT + OUT), body.get_corner(UP + RIGHT + OUT), color=BLACK, stroke_width=2) self.axis = axis self.add(body, screen_plate, axis) self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN) self.rotate(np.pi / 6, DOWN, about_point=ORIGIN)
def get_number_design(self, value, symbol): num = int(value) n_rows = { 2: 2, 3: 3, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3, 9: 4, 10: 4, }[num] n_cols = 1 if num in [2, 3] else 2 insertion_indices = { 5: [0], 7: [0], 8: [0, 1], 9: [1], 10: [0, 2], }.get(num, []) top = self.get_top() + symbol.get_height() * DOWN bottom = self.get_bottom() + symbol.get_height() * UP column_points = [ interpolate(top, bottom, alpha) for alpha in np.linspace(0, 1, n_rows) ] design = VGroup(*[ symbol.copy().move_to(point) for point in column_points ]) if n_cols == 2: space = 0.2 * self.get_width() column_copy = design.copy().shift(space * RIGHT) design.shift(space * LEFT) design.add(*column_copy) design.add(*[ symbol.copy().move_to( center_of_mass(column_points[i:i + 2]) ) for i in insertion_indices ]) for symbol in design: if symbol.get_center()[1] < self.get_center()[1]: symbol.rotate_in_place(np.pi) return design
def get_number_design(self, value, symbol): num = int(value) n_rows = { 2: 2, 3: 3, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3, 9: 4, 10: 4, }[num] n_cols = 1 if num in [2, 3] else 2 insertion_indices = { 5: [0], 7: [0], 8: [0, 1], 9: [1], 10: [0, 2], }.get(num, []) top = self.get_top() + symbol.get_height() * DOWN bottom = self.get_bottom() + symbol.get_height() * UP column_points = [ interpolate(top, bottom, alpha) for alpha in np.linspace(0, 1, n_rows) ] design = VGroup(*[ symbol.copy().move_to(point) for point in column_points ]) if n_cols == 2: space = 0.2 * self.get_width() column_copy = design.copy().shift(space * RIGHT) design.shift(space * LEFT) design.add(*column_copy) design.add(*[ symbol.copy().move_to( center_of_mass(column_points[i:i + 2]) ) for i in insertion_indices ]) for symbol in design: if symbol.get_center()[1] < self.get_center()[1]: symbol.rotate_in_place(np.pi) return design
def create_label(self): for tex, value in zip(self.labels, self.values): i = self.labels.index(tex) r = self.inner_radius + self.outer_radius_func(self.values[i]) size = TAU * r / len(self.values) * 0.2 tex_i = Text(tex, font=self.label_font, color=WHITE, plot_depth=1).set_height(size) value_i = Text(str(value), font=self.label_font, color=WHITE, plot_depth=1).set_height(size).next_to(tex_i, DOWN * 0.64 * size) if not self.unit == None: unit_i = Text(self.unit, font=self.label_font, color=WHITE, plot_depth=1).set_height(size).next_to(value_i, RIGHT * 0.2 * size) VGroup(value_i, unit_i).next_to(tex_i, DOWN * 0.64 * size) label_i = VGroup(tex_i, value_i, unit_i) else: label_i = VGroup(tex_i, value_i) angle = TAU/len(self.values) start_a = np.angle(complex(*self.start_direction[0:2])) self.labels_group.add(label_i.shift(self.center + complex_to_R3((r-size * 1.2-r*0.05) * np.exp(1j * (start_a + (i + 0.5) * TAU/len(self.values)))))) return self.labels_group
def move_tip_to(self, point): mover = VGroup(self) if self.content is not None: mover.add(self.content) mover.shift(point - self.get_tip()) return self
class CountingScene(Scene): CONFIG = { "digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], "counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP, "count_dot_starting_radius": 0.5, "dot_configuration_height": 2, "ones_configuration_location": UP + 2 * RIGHT, "num_scale_factor": 2, "num_start_location": 2 * DOWN, } def setup(self): self.dots = VGroup() self.number = 0 self.max_place = 0 self.number_mob = VGroup(TexMobject(str(self.number))) self.number_mob.scale(self.num_scale_factor) self.number_mob.shift(self.num_start_location) self.dot_templates = [] self.dot_template_iterators = [] self.curr_configurations = [] self.arrows = VGroup() self.add(self.number_mob) def get_template_configuration(self, place): # This should probably be replaced for non-base-10 counting scenes down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN result = [] for down_right_steps in range(5): for left_steps in range(down_right_steps): result.append(down_right_steps * down_right + left_steps * LEFT) return reversed(result[:self.get_place_max(place)]) def get_dot_template(self, place): # This should be replaced for non-base-10 counting scenes dots = VGroup(*[ Dot( point, radius=0.25, fill_opacity=0, stroke_width=2, stroke_color=WHITE, ) for point in self.get_template_configuration(place) ]) dots.set_height(self.dot_configuration_height) return dots def add_configuration(self): new_template = self.get_dot_template(len(self.dot_templates)) new_template.move_to(self.ones_configuration_location) left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT new_template.shift(left_vect * len(self.dot_templates)) self.dot_templates.append(new_template) self.dot_template_iterators.append(it.cycle(new_template)) self.curr_configurations.append(VGroup()) def count(self, max_val, run_time_per_anim=1): for x in range(max_val): self.increment(run_time_per_anim) def increment(self, run_time_per_anim=1): moving_dot = Dot( self.counting_dot_starting_position, radius=self.count_dot_starting_radius, color=self.digit_place_colors[0], ) moving_dot.generate_target() moving_dot.set_fill(opacity=0) kwargs = {"run_time": run_time_per_anim} continue_rolling_over = True first_move = True place = 0 while continue_rolling_over: added_anims = [] if first_move: added_anims += self.get_digit_increment_animations() first_move = False moving_dot.target.replace(next(self.dot_template_iterators[place])) self.play(MoveToTarget(moving_dot), *added_anims, **kwargs) self.curr_configurations[place].add(moving_dot) if len(self.curr_configurations[place].split() ) == self.get_place_max(place): full_configuration = self.curr_configurations[place] self.curr_configurations[place] = VGroup() place += 1 center = full_configuration.get_center_of_mass() radius = 0.6 * max( full_configuration.get_width(), full_configuration.get_height(), ) circle = Circle( radius=radius, stroke_width=0, fill_color=self.digit_place_colors[place], fill_opacity=0.5, ) circle.move_to(center) moving_dot = VGroup(circle, full_configuration) moving_dot.generate_target() moving_dot[0].set_fill(opacity=0) else: continue_rolling_over = False def get_digit_increment_animations(self): result = [] self.number += 1 is_next_digit = self.is_next_digit() if is_next_digit: self.max_place += 1 new_number_mob = self.get_number_mob(self.number) new_number_mob.move_to(self.number_mob, RIGHT) if is_next_digit: self.add_configuration() place = len(new_number_mob.split()) - 1 result.append(FadeIn(self.dot_templates[place])) arrow = Arrow(new_number_mob[place].get_top(), self.dot_templates[place].get_bottom(), color=self.digit_place_colors[place]) self.arrows.add(arrow) result.append(ShowCreation(arrow)) result.append(Transform(self.number_mob, new_number_mob, lag_ratio=0.5)) return result def get_number_mob(self, num): result = VGroup() place = 0 max_place = self.max_place while place < max_place: digit = TexMobject(str(self.get_place_num(num, place))) if place >= len(self.digit_place_colors): self.digit_place_colors += self.digit_place_colors digit.set_color(self.digit_place_colors[place]) digit.scale(self.num_scale_factor) digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN) result.add(digit) place += 1 return result def is_next_digit(self): return False def get_place_num(self, num, place): return 0 def get_place_max(self, place): return 0
class CountingScene(Scene): CONFIG = { "digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], "counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP, "count_dot_starting_radius": 0.5, "dot_configuration_height": 2, "ones_configuration_location": UP + 2 * RIGHT, "num_scale_factor": 2, "num_start_location": 2 * DOWN, } def setup(self): self.dots = VGroup() self.number = 0 self.max_place = 0 self.number_mob = VGroup(TexMobject(str(self.number))) self.number_mob.scale(self.num_scale_factor) self.number_mob.shift(self.num_start_location) self.dot_templates = [] self.dot_template_iterators = [] self.curr_configurations = [] self.arrows = VGroup() self.add(self.number_mob) def get_template_configuration(self, place): # This should probably be replaced for non-base-10 counting scenes down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN result = [] for down_right_steps in range(5): for left_steps in range(down_right_steps): result.append( down_right_steps * down_right + left_steps * LEFT ) return reversed(result[:self.get_place_max(place)]) def get_dot_template(self, place): # This should be replaced for non-base-10 counting scenes dots = VGroup(*[ Dot( point, radius=0.25, fill_opacity=0, stroke_width=2, stroke_color=WHITE, ) for point in self.get_template_configuration(place) ]) dots.set_height(self.dot_configuration_height) return dots def add_configuration(self): new_template = self.get_dot_template(len(self.dot_templates)) new_template.move_to(self.ones_configuration_location) left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT new_template.shift( left_vect * len(self.dot_templates) ) self.dot_templates.append(new_template) self.dot_template_iterators.append( it.cycle(new_template) ) self.curr_configurations.append(VGroup()) def count(self, max_val, run_time_per_anim=1): for x in range(max_val): self.increment(run_time_per_anim) def increment(self, run_time_per_anim=1): moving_dot = Dot( self.counting_dot_starting_position, radius=self.count_dot_starting_radius, color=self.digit_place_colors[0], ) moving_dot.generate_target() moving_dot.set_fill(opacity=0) kwargs = { "run_time": run_time_per_anim } continue_rolling_over = True first_move = True place = 0 while continue_rolling_over: added_anims = [] if first_move: added_anims += self.get_digit_increment_animations() first_move = False moving_dot.target.replace( next(self.dot_template_iterators[place]) ) self.play(MoveToTarget(moving_dot), *added_anims, **kwargs) self.curr_configurations[place].add(moving_dot) if len(self.curr_configurations[place].split()) == self.get_place_max(place): full_configuration = self.curr_configurations[place] self.curr_configurations[place] = VGroup() place += 1 center = full_configuration.get_center_of_mass() radius = 0.6 * max( full_configuration.get_width(), full_configuration.get_height(), ) circle = Circle( radius=radius, stroke_width=0, fill_color=self.digit_place_colors[place], fill_opacity=0.5, ) circle.move_to(center) moving_dot = VGroup(circle, full_configuration) moving_dot.generate_target() moving_dot[0].set_fill(opacity=0) else: continue_rolling_over = False def get_digit_increment_animations(self): result = [] self.number += 1 is_next_digit = self.is_next_digit() if is_next_digit: self.max_place += 1 new_number_mob = self.get_number_mob(self.number) new_number_mob.move_to(self.number_mob, RIGHT) if is_next_digit: self.add_configuration() place = len(new_number_mob.split()) - 1 result.append(FadeIn(self.dot_templates[place])) arrow = Arrow( new_number_mob[place].get_top(), self.dot_templates[place].get_bottom(), color=self.digit_place_colors[place] ) self.arrows.add(arrow) result.append(ShowCreation(arrow)) result.append(Transform( self.number_mob, new_number_mob, lag_ratio=0.5 )) return result def get_number_mob(self, num): result = VGroup() place = 0 max_place = self.max_place while place < max_place: digit = TexMobject(str(self.get_place_num(num, place))) if place >= len(self.digit_place_colors): self.digit_place_colors += self.digit_place_colors digit.set_color(self.digit_place_colors[place]) digit.scale(self.num_scale_factor) digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN) result.add(digit) place += 1 return result def is_next_digit(self): return False def get_place_num(self, num, place): return 0 def get_place_max(self, place): return 0
class Code(VGroup): CONFIG = { "tab_width": 3, "line_spacing": 0.1, "scale_factor": 0.5, "run_time": 1, "font": 'Monospac821 BT', 'stroke_width': 0, 'margin': 0.3, 'indentation_char': " ", "background": "rectangle", # or window "corner_radius": 0.2, 'insert_line_no': True, 'line_no_from': 1, "line_no_buff": 0.4, 'style': 'vim', 'language': 'cpp', 'generate_html_file': False } def __init__(self, file_name=None, **kwargs): Container.__init__(self, **kwargs) self.file_name = file_name or self.file_name self.ensure_valid_file() self.style = self.style.lower() self.gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12:strati + 19] self.gen_code_json() self.code = self.gen_colored_lines() if self.insert_line_no: self.line_numbers = self.gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code self.background_mobject = SurroundingRectangle( forground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=0, fill_opacity=1, ) self.background_mobject.round_corners(self.corner_radius) else: if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code height = forground.get_height() + 0.1 * 3 + 2 * self.margin width = forground.get_width() + 0.1 * 3 + 2 * self.margin rrect = RoundedRectangle(corner_radius=self.corner_radius, height=height, width=width, stroke_width=0, color=self.background_color, fill_opacity=1) red_button = Dot(radius=0.1, stroke_width=0, color='#ff5f56') red_button.shift(LEFT * 0.1 * 3) yellow_button = Dot(radius=0.1, stroke_width=0, color='#ffbd2e') green_button = Dot(radius=0.1, stroke_width=0, color='#27c93f') green_button.shift(RIGHT * 0.1 * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05)) self.background_mobject = VGroup(rrect, buttons) x = (height - forground.get_height()) / 2 - 0.1 * 3 self.background_mobject.shift(forground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: VGroup.__init__(self, self.background_mobject, self.line_numbers, *self.code, **kwargs) else: VGroup.__init__(self, self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), *self.code, **kwargs) self.move_to(np.array([0, 0, 0])) def apply_points_function_about_point(self, func, about_point=None, about_edge=None): if about_point is None: if about_edge is None: about_edge = self.get_corner(UP + LEFT) about_point = self.get_critical_point(about_edge) for mob in self.family_members_with_points(): mob.points -= about_point mob.points = func(mob.points) mob.points += about_point return self def ensure_valid_file(self): if self.file_name is None: raise Exception("Must specify file for Code") possible_paths = [ os.path.join(os.path.join("assets", "codes"), self.file_name), self.file_name, ] for path in possible_paths: if os.path.exists(path): self.file_path = path return raise IOError("No file matching %s in codes directory" % self.file_name) def gen_line_numbers(self): line_numbers_array = [] for line_no in range(0, self.code_json.__len__()): number = str(self.line_no_from + line_no) line_numbers_array.append(number) line_numbers = Paragraph(*[i for i in line_numbers_array], line_spacing=self.line_spacing, alignment="right", font=self.font, stroke_width=self.stroke_width).scale( self.scale_factor) return line_numbers def gen_colored_lines(self): lines_text = [] for line_no in range(0, self.code_json.__len__()): line_str = "" for word_index in range(self.code_json[line_no].__len__()): line_str = line_str + self.code_json[line_no][word_index][0] lines_text.append(self.tab_spaces[line_no] * "\t" + line_str) code = Paragraph(*[i for i in lines_text], line_spacing=self.line_spacing, tab_width=self.tab_width, alignment="left", font=self.font, stroke_width=self.stroke_width).scale( self.scale_factor) for line_no in range(code.__len__()): line = code[line_no] line_char_index = self.tab_spaces[line_no] for word_index in range(self.code_json[line_no].__len__()): line[line_char_index:line_char_index + self. code_json[line_no][word_index][0].__len__()].set_color( self.code_json[line_no][word_index][1]) line_char_index += self.code_json[line_no][word_index][ 0].__len__() return code def gen_html_string(self): file = open(self.file_path, "r") code_str = file.read() file.close() self.html_string = hilite_me( code_str, self.language, {}, self.style, self.insert_line_no, "border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;" ) if self.generate_html_file: os.makedirs(os.path.join("assets", "codes", "generated_html_files"), exist_ok=True) file = open( os.path.join("assets", "codes", "generated_html_files", self.file_name + ".html"), "w") file.write(self.html_string) file.close() def gen_code_json(self): if self.background_color == "#111111" or \ self.background_color == "#272822" or \ self.background_color == "#202020" or \ self.background_color == "#000000": self.default_color = "#ffffff" else: self.default_color = "#000000" for i in range(3, -1, -1): self.html_string = self.html_string.replace("</" + " " * i, "</") for i in range(10, -1, -1): self.html_string = self.html_string.replace( "</span>" + " " * i, " " * i + "</span>") self.html_string = self.html_string.replace("background-color:", "background:") if self.insert_line_no: start_point = self.html_string.find("</td><td><pre") start_point = start_point + 9 else: start_point = self.html_string.find("<pre") self.html_string = self.html_string[start_point:] # print(self.html_string) lines = self.html_string.split("\n") lines = lines[0:lines.__len__() - 2] start_point = lines[0].find(">") lines[0] = lines[0][start_point + 1:] # print(lines) self.code_json = [] self.tab_spaces = [] code_json_line_index = -1 for line_index in range(0, lines.__len__()): if lines[line_index].__len__() == 0: continue # print(lines[line_index]) self.code_json.append([]) code_json_line_index = code_json_line_index + 1 if lines[line_index].startswith(self.indentation_char): start_point = lines[line_index].find("<") starting_string = lines[line_index][:start_point] indentation_char_count = lines[line_index][:start_point].count( self.indentation_char) if starting_string.__len__( ) != indentation_char_count * self.indentation_char.__len__(): lines[line_index] = "\t" * indentation_char_count + starting_string[starting_string.rfind( self.indentation_char) + self.indentation_char.__len__():] + \ lines[line_index][start_point:] else: lines[line_index] = "\t" * indentation_char_count + lines[ line_index][start_point:] indentation_char_count = 0 while lines[line_index][indentation_char_count] == '\t': indentation_char_count = indentation_char_count + 1 self.tab_spaces.append(indentation_char_count) # print(lines[line_index]) lines[line_index] = self.correct_non_span(lines[line_index]) # print(lines[line_index]) words = lines[line_index].split("<span") for word_index in range(1, words.__len__()): color_index = words[word_index].find("color:") if color_index == -1: color = self.default_color else: starti = words[word_index][color_index:].find("#") color = words[word_index][color_index + starti:color_index + starti + 7] start_point = words[word_index].find(">") end_point = words[word_index].find("</span>") text = words[word_index][start_point + 1:end_point] text = html.unescape(text) if text != "": # print(text, "'" + color + "'") self.code_json[code_json_line_index].append([text, color]) # print(self.code_json) def correct_non_span(self, line_str): words = line_str.split("</span>") line_str = "" for i in range(0, words.__len__()): if i != words.__len__() - 1: j = words[i].find("<span") else: j = words[i].__len__() temp = "" starti = -1 for k in range(0, j): if words[i][k] == "\t" and starti == -1: continue else: if starti == -1: starti = k temp = temp + words[i][k] if temp != "": if i != words.__len__() - 1: temp = '<span style="color:' + self.default_color + '">' + words[ i][starti:j] + "</span>" else: temp = '<span style="color:' + self.default_color + '">' + words[ i][starti:j] temp = temp + words[i][j:] words[i] = temp if words[i] != "": line_str = line_str + words[i] + "</span>" return line_str
def move_tip_to(self, point): mover = VGroup(self) if self.content is not None: mover.add(self.content) mover.shift(point - self.get_tip()) return self
class Code(VGroup): CONFIG = { "tab_width": 1, "line_spacing": 0.3, # space between the lines "scale_factor": 0.4, "run_time": 1, #"font": 'Monospac821 BT', #"font": 'Arial', #"font": "DejaVu Sans Mono", "font": "Courier New", #"font": 'Courier New', "font_size": 48, 'stroke_width': 0, 'margin': 0.3, 'indentation_char': " ", "background": "window", #rectangle or window "corner_radius": 0.2, 'insert_line_no': True, 'line_no_from': 1, "line_no_buff": 0.4, 'style': 'railscasts', #white based:railscasts, vim , material, native; black based: tango, xcode ; #pink-based: paraiso-light; styles not highlithing print() and list(): monokai, fruity, paraiso-dark #grey-based: arduino, solarized-dark, solarized-light #beige-based: inkpot, zenburn 'language': 'python', 'generate_html_file': False, 'adapting_x': False, 'adapting_y': False, 'center_y': True, } def __init__(self, file_name=None, code_str=None, **kwargs): Container.__init__(self, **kwargs) self.file_name = file_name self.code_str = code_str self.ensure_valid_file() self.style = self.style.lower() self.gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12:strati + 19] self.gen_code_json() self.code = self.gen_colored_lines() if self.insert_line_no: self.line_numbers = self.gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code self.background_mobject = SurroundingRectangle( forground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=0, fill_opacity=1, ) self.background_mobject.round_corners(self.corner_radius) else: if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code height = forground.get_height() + 0.1 * 3 + 2 * self.margin width = forground.get_width() + 0.1 * 3 + 2 * self.margin rrect = RoundedRectangle(corner_radius=self.corner_radius, height=height, width=width, stroke_width=0, color=GREY_E, fill_opacity=1) red_button = Dot(radius=0.1, stroke_width=0, color='#ff5f56') red_button.shift(LEFT * 0.1 * 3) yellow_button = Dot(radius=0.1, stroke_width=0, color='#ffbd2e') green_button = Dot(radius=0.1, stroke_width=0, color='#27c93f') green_button.shift(RIGHT * 0.1 * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05)) self.background_mobject = VGroup(rrect, buttons) x = (height - forground.get_height()) / 2 - 0.1 * 3 self.background_mobject.shift(forground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: VGroup.__init__(self, self.background_mobject, self.line_numbers, *self.code, **kwargs) else: VGroup.__init__(self, self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), *self.code, **kwargs) window = RIGHT_SIDE[0] - LEFT_SIDE[0] width = self.background_mobject.get_width() print(width) scale_x = window / width * 0.96 window = TOP[1] - BOTTOM[1] print(window) width = self.background_mobject.get_height() scale_y = window / width * 0.96 if self.adapting_x and scale_x < scale_y: self.scale(scale_x) if self.adapting_y and scale_y <= scale_x: self.scale(scale_y) self.align_to(LEFT_SIDE + self.margin, LEFT) self.align_to( TOP - self.margin + 0.42 * (self.code.char_height + self.code.line_spacing) * self.scale_factor, UP) def FadeInWindow(self): return FadeIn(self.background_mobject), ''' def Write(self): write_code = [Write(line) for line in self.code.lines[0]] write_line_numbers = [Write(number) for number in self.line_numbers.lines[0]] write_all = [item for sublist in zip(write_line_numbers, write_code) for item in sublist] return write_all def ShowCreation(self): write_code = [ShowCreation(line, duration= 1) for line in self.code.lines[0]] write_line_numbers = [ShowCreation(number) for number in self.line_numbers.lines[0]] write_all = [item for sublist in zip(write_line_numbers, write_code) for item in sublist] return write_all ''' def center_to_code(self): if self.center_y: print(self.get_y() - self.code.lines[0][0].get_height() + self.code.line_spacing) self.set_y(self.get_y() + (self.code.char_height + self.code.line_spacing) * self.scale_factor) def apply_points_function_about_point(self, func, about_point=None, about_edge=None): if about_point is None: if about_edge is None: about_edge = self.get_corner(UP + LEFT) about_point = self.get_critical_point(about_edge) for mob in self.family_members_with_points(): mob.points -= about_point mob.points = func(mob.points) mob.points += about_point return self def ensure_valid_file(self): if not self.file_name is None: possible_paths = [ os.path.join(os.path.join("assets", "codes"), self.file_name), self.file_name, ] for path in possible_paths: if os.path.exists(path): self.file_path = path return raise IOError("No file matching %s in codes directory" % self.file_name) def gen_line_numbers(self): line_numbers_array = [] for line_no in range(0, self.code_json.__len__()): number = str(self.line_no_from + line_no) line_numbers_array.append(number) #color ="#E6E8E8" line_numbers = Paragraph( *[i for i in line_numbers_array], line_spacing=self.line_spacing, alignment="left", font=self.font, stroke_width=self.stroke_width, ).scale(self.scale_factor) return line_numbers def gen_colored_lines(self): lines_text = [] for line_no in range(0, self.code_json.__len__()): line_str = "" for word_index in range(self.code_json[line_no].__len__()): line_str = line_str + self.code_json[line_no][word_index][0] lines_text.append(self.tab_spaces[line_no] * "\t" + line_str) #print(lines_text) code = Paragraph(*[text for text in lines_text], line_spacing=self.line_spacing, tab_width=self.tab_width, alignment="left", font=self.font, stroke_width=self.stroke_width, unpack_groups=True).scale(self.scale_factor) for line_no in range(code.__len__()): line = code[line_no] line_char_index = self.tab_spaces[line_no] for word_index in range(self.code_json[line_no].__len__()): line[line_char_index:line_char_index + self. code_json[line_no][word_index][0].__len__()].set_color( self.code_json[line_no][word_index][1]) line_char_index += self.code_json[line_no][word_index][ 0].__len__() #print(self.code_json) return code def gen_html_string(self): if self.file_name: file = open(self.file_path, "r") code_str = file.read() file.close() if self.code_str: code_str = self.code_str self.html_string = hilite_me( code_str, self.language, {}, self.style, self.insert_line_no, "border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;" ) if self.generate_html_file: os.makedirs(os.path.join("assets", "codes", "generated_html_files"), exist_ok=True) file = open( os.path.join("assets", "codes", "generated_html_files", self.file_name + ".html"), "w") file.write(self.html_string) file.close() def gen_code_json(self): #print(self.html_string) if self.background_color == "#111111" or \ self.background_color == "#272822" or \ self.background_color == "#202020" or \ self.background_color == "#000000": self.default_color = "#ffffff" else: self.default_color = "#000000" for i in range(3, -1, -1): self.html_string = self.html_string.replace("</" + " " * i, "</") for i in range(10, -1, -1): self.html_string = self.html_string.replace( "</span>" + " " * i, " " * i + "</span>") self.html_string = self.html_string.replace("background-color:", "background:") if self.insert_line_no: start_point = self.html_string.find("</td><td><pre") start_point = start_point + 9 else: start_point = self.html_string.find("<pre") self.html_string = self.html_string[start_point:] # print(self.html_string) lines = self.html_string.split("\n") lines = lines[0:lines.__len__() - 2] start_point = lines[0].find(">") lines[0] = lines[0][start_point + 1:] # print(lines) self.code_json = [] self.tab_spaces = [] code_json_line_index = -1 #print(self.html_string) starting_string = "" for line_index in range(0, lines.__len__()): if lines[line_index].__len__() == 0: continue #print(lines[line_index]) self.code_json.append([]) code_json_line_index = code_json_line_index + 1 if lines[line_index].startswith(self.indentation_char): start_point = lines[line_index].find("<") starting_string = lines[line_index][:start_point] indentation_char_count = lines[line_index][:start_point].count( self.indentation_char) if starting_string.__len__( ) != indentation_char_count * self.indentation_char.__len__(): lines[line_index] = "\t" * indentation_char_count + starting_string[starting_string.rfind( self.indentation_char) + self.indentation_char.__len__():] + \ lines[line_index][start_point:] else: lines[line_index] = "\t" * indentation_char_count + lines[ line_index][start_point:] indentation_char_count = 0 while lines[line_index][indentation_char_count] == '\t': indentation_char_count = indentation_char_count + 1 self.tab_spaces.append(indentation_char_count) # print(lines[line_index]) lines[line_index] = self.correct_non_span(lines[line_index]) # print(lines[line_index]) words = lines[line_index].split("<span") for word_index in range(1, words.__len__()): color_index = words[word_index].find("color:") if color_index == -1: color = self.default_color else: starti = words[word_index][color_index:].find("#") color = words[word_index][color_index + starti:color_index + starti + 7] start_point = words[word_index].find(">") end_point = words[word_index].find("</span>") text = words[word_index][start_point + 1:end_point] text = html.unescape(text) if text != "": #print(text, "'" + color + "'") self.code_json[code_json_line_index].append([text, color]) #print(self.code_json) def correct_non_span(self, line_str): words = line_str.split("</span>") line_str = "" for i in range(0, words.__len__()): if i != words.__len__() - 1: j = words[i].find("<span") else: j = words[i].__len__() temp = "" starti = -1 for k in range(0, j): if words[i][k] == "\t" and starti == -1: continue else: if starti == -1: starti = k temp = temp + words[i][k] if temp != "": if i != words.__len__() - 1: temp = '<span style="color:' + self.default_color + '">' + words[ i][starti:j] + "</span>" else: temp = '<span style="color:' + self.default_color + '">' + words[ i][starti:j] temp = temp + words[i][j:] words[i] = temp if words[i] != "": line_str = line_str + words[i] + "</span>" return line_str
def __init__(self, file_name=None, code_str=None, **kwargs): Container.__init__(self, **kwargs) self.file_name = file_name self.code_str = code_str self.ensure_valid_file() self.style = self.style.lower() self.gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12:strati + 19] self.gen_code_json() self.code = self.gen_colored_lines() if self.insert_line_no: self.line_numbers = self.gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code self.background_mobject = SurroundingRectangle( forground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=0, fill_opacity=1, ) self.background_mobject.round_corners(self.corner_radius) else: if self.insert_line_no: forground = VGroup(self.code, self.line_numbers) else: forground = self.code height = forground.get_height() + 0.1 * 3 + 2 * self.margin width = forground.get_width() + 0.1 * 3 + 2 * self.margin rrect = RoundedRectangle(corner_radius=self.corner_radius, height=height, width=width, stroke_width=0, color=GREY_E, fill_opacity=1) red_button = Dot(radius=0.1, stroke_width=0, color='#ff5f56') red_button.shift(LEFT * 0.1 * 3) yellow_button = Dot(radius=0.1, stroke_width=0, color='#ffbd2e') green_button = Dot(radius=0.1, stroke_width=0, color='#27c93f') green_button.shift(RIGHT * 0.1 * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05)) self.background_mobject = VGroup(rrect, buttons) x = (height - forground.get_height()) / 2 - 0.1 * 3 self.background_mobject.shift(forground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: VGroup.__init__(self, self.background_mobject, self.line_numbers, *self.code, **kwargs) else: VGroup.__init__(self, self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), *self.code, **kwargs) window = RIGHT_SIDE[0] - LEFT_SIDE[0] width = self.background_mobject.get_width() print(width) scale_x = window / width * 0.96 window = TOP[1] - BOTTOM[1] print(window) width = self.background_mobject.get_height() scale_y = window / width * 0.96 if self.adapting_x and scale_x < scale_y: self.scale(scale_x) if self.adapting_y and scale_y <= scale_x: self.scale(scale_y) self.align_to(LEFT_SIDE + self.margin, LEFT) self.align_to( TOP - self.margin + 0.42 * (self.code.char_height + self.code.line_spacing) * self.scale_factor, UP)