def build_layout(self):
        # internal static object cannot fall out of the layer
        # otherwise the code will crash
        col = math.ceil(self.width / GRID_WIDTH)
        row = math.ceil(self.height / GRID_HEIGHT)
        layout = [[None for c in range(col)] for r in range(row)]

        if hasattr(self, "contents"):
            for key, (obj, pos, dim) in self.contents.items():  # O(A)
                if dim == (0, 0):
                    continue
                row_tl, col_tl = find_point_loc(pos, layout)
                dim = dim[0] - GRID_WIDTH, dim[1] - GRID_HEIGHT
                row_br, col_br = find_point_loc(calc_offset(pos, dim), layout)
                assert row_tl <= row_br, "Building layout problem{0}{1}".format(
                    row_tl, row_br)
                assert col_tl <= col_tl, "Building layout problem{0}{1}".format(
                    col_tl, col_br)
                for r in range(row_tl, row_br + 1):
                    for c in range(col_tl, col_br + 1):
                        layout[r][c] = key

            self.layout = layout
        else:
            raise Exception(
                "contents should be created before build_layout method is called, {0}"
                .format(self))

        return None
 def draw_box(self, surface, offset):
     super().draw_box_bg(surface, offset)
     for key, (obj, pos, dim) in self.contents.items():
         if key == "exit":
             new_offset = calc_offset(offset, pos)
             obj.draw_box(surface, new_offset)
         else:
             surface.blit(obj, pos)
    def draw_box(self, surface, offset):  # O(n)
        self.draw_box_bg(surface, offset)
        if hasattr(self, "contents"):
            for name, (obj, rel_pos, dim) in self.contents.items():
                new_offset = calc_offset(offset, rel_pos)
                obj.draw_box(surface, new_offset)

        if hasattr(self, "objects"):
            for key, objs in self.objects.items():
                for obj in objs:
                    obj.draw_box(surface, offset)
    def draw(self, surface, offset):  # O(n)
        """
        :param surface: pygame.surface
        :param offset: menu's abs top-left pos
        :return: None
        """
        self.draw_bg(surface, offset)
        if hasattr(self, "contents"):
            for name, (obj, rel_pos, dim) in self.contents.items():
                new_offset = calc_offset(offset, rel_pos)
                obj.draw(surface, new_offset)

        if hasattr(self, "objects"):
            for key, objs in self.objects.items():
                for obj in objs:
                    obj.draw(surface, offset)
    def run(self, click_command, mouse_pos,
            offset):  # mouse_pos => abs_mouse_pos
        # handle background commands
        for command in self.get_commands():
            self.handle_command(command)

        # run the static game if self.hover
        rel_mouse_pos = calc_rel_pos(mouse_pos, offset)
        # check hover
        for key, (obj, ref, dim) in self.contents.items():
            if isInside(rel_mouse_pos,
                        calcVertices(ref[0], ref[1], dim[0], dim[1])):
                if self.mouse_is_on != key:
                    if self.mouse_is_on is not None and hasattr(
                            self.contents[self.mouse_is_on][0], "un_hover"):
                        self.contents[self.mouse_is_on][0].un_hover()
                    if hasattr(obj, "do_hover"):
                        obj.do_hover()
                    self.mouse_is_on = key
                break
        else:
            if self.mouse_is_on is not None and hasattr(
                    self.contents[self.mouse_is_on][0], "un_hover"):
                self.contents[self.mouse_is_on][0].un_hover()
            self.mouse_is_on = None

        if click_command == 1:
            if not isInside(rel_mouse_pos,
                            calcVertices(0, 0, self.width, self.height)):
                # return OFF_FOCUS, self
                return RETURN, None
            else:  # otherwise call content button
                if self.mouse_is_on is not None and hasattr(
                        self.contents[self.mouse_is_on][0], "onclick"):
                    obj, ref, dim = self.contents[self.mouse_is_on]
                    new_offset = calc_offset(offset, ref)
                    # when onclick, reset the button to its initial state
                    self.contents[self.mouse_is_on][0].un_hover(
                    )  # assert a clickable object must be hoverable
                    self.mouse_is_on = None
                    return obj.onclick(click_command, mouse_pos, new_offset)
        return None, None