示例#1
0
 def redraw_from_text_block(self):
     """
     Redraws the final parts of the text box element that don't include redrawing the actual
     text. Useful if we've just moved the position of the text (say, with a scroll bar)
     without actually changing the text itself.
     """
     if self.rect.width <= 0 or self.rect.height <= 0:
         return
     if self.scroll_bar is not None:
         height_adjustment = int(
             self.scroll_bar.start_percentage *
             self.formatted_text_block.final_dimensions[1])
     else:
         height_adjustment = 0
     drawable_area_size = (max(
         1,
         (self.rect[2] - (self.padding[0] * 2) - (self.border_width * 2) -
          (self.shadow_width * 2) - (2 * self.rounded_corner_offset))),
                           max(1, (self.rect[3] - (self.padding[1] * 2) -
                                   (self.border_width * 2) -
                                   (self.shadow_width * 2) -
                                   (2 * self.rounded_corner_offset))))
     drawable_area = pygame.Rect((0, height_adjustment), drawable_area_size)
     new_image = pygame.surface.Surface(self.rect.size,
                                        flags=pygame.SRCALPHA,
                                        depth=32)
     new_image.fill(pygame.Color(0, 0, 0, 0))
     basic_blit(new_image, self.background_surf, (0, 0))
     basic_blit(
         new_image, self.formatted_text_block.block_sprite,
         (self.padding[0] + self.border_width + self.shadow_width +
          self.rounded_corner_offset, self.padding[1] + self.border_width +
          self.shadow_width + self.rounded_corner_offset), drawable_area)
     self.set_image(new_image)
示例#2
0
    def set_image(self, new_image: Union[pygame.surface.Surface, None]):
        """
        Wraps setting the image variable of this element so that we also set the current image
        clip on the image at the same time.

        :param new_image: The new image to set.

        """
        if self.get_image_clipping_rect() is not None and new_image is not None:
            self._pre_clipped_image = new_image
            if (self.get_image_clipping_rect().width == 0 and
                    self.get_image_clipping_rect().height == 0):
                self.image = self.ui_manager.get_universal_empty_surface()
            else:
                self.image = pygame.surface.Surface(self._pre_clipped_image.get_size(),
                                                    flags=pygame.SRCALPHA,
                                                    depth=32)
                self.image.fill(pygame.Color('#00000000'))
                basic_blit(self.image,
                           self._pre_clipped_image,
                           self.get_image_clipping_rect(),
                           self.get_image_clipping_rect())
        else:
            self.image = new_image.copy() if new_image is not None else None
            self._pre_clipped_image = None
示例#3
0
    def redraw_from_chunks(self, text_effect: Union[TextBoxEffect, None]):
        """
        Redraw only the last part of text block starting from the already complete styled and word
        wrapped StyledChunks.

        :param text_effect: The text effect to use when redrawing.
        """
        final_alpha = text_effect.get_final_alpha() if text_effect else 255
        self.block_sprite = pygame.surface.Surface((self.width, self.height),
                                                   flags=pygame.SRCALPHA,
                                                   depth=32)
        self.block_sprite.fill(pygame.Color('#00000000'))

        if isinstance(self.bg_colour, ColourGradient):
            self.block_sprite.fill(pygame.Color("#FFFFFFFF"))
            self.bg_colour.apply_gradient_to_surface(self.block_sprite)
        else:
            self.block_sprite.fill(self.bg_colour)

        for text_line in self.lines:
            for chunk in text_line.chunks:
                if self.block_sprite is not None:
                    basic_blit(self.block_sprite, chunk.rendered_chunk,
                               chunk.rect)
        self.block_sprite.set_alpha(final_alpha)
示例#4
0
    def finalise_images_and_text(self, image_state_str: str, state_str: str,
                                 text_colour_state_str: str,
                                 text_shadow_colour_state_str: str,
                                 add_text: bool):
        """
        Rebuilds any text or image used by a specific state in the drawable shape. Effectively
        this means adding them on top of whatever is already in the state's surface. As such it
        should generally be called last in the process of building up a finished drawable shape
        state.

        :param add_text:
        :param image_state_str: image ID of the state we are going to be adding images and text to.
        :param state_str: normal ID of the state we are going to be adding images and text to.
        :param text_colour_state_str: text ID of the state we are going to be adding images and
                                      text to.
        :param text_shadow_colour_state_str: text shadow ID of the state we are going to be adding
                                             images and text to.

        """
        # Draw any themed images
        if image_state_str in self.theming and self.theming[
                image_state_str] is not None:
            image_rect = self.theming[image_state_str].get_rect()
            image_rect.center = (int(self.containing_rect.width / 2),
                                 int(self.containing_rect.height / 2))
            basic_blit(self.states[state_str].surface,
                       self.theming[image_state_str], image_rect)
        if add_text:
            self.finalise_text(state_str, text_colour_state_str,
                               text_shadow_colour_state_str)
示例#5
0
    def set_image_clip(self, rect: Union[pygame.Rect, None]):
        """
        Sets a clipping rectangle on this element's image determining what portion of it will
        actually be displayed when this element is blitted to the screen.

        :param rect: A clipping rectangle, or None to clear the clip.

        """
        if rect is not None:
            if rect.width < 0:
                rect.width = 0
            if rect.height < 0:
                rect.height = 0

            if self._pre_clipped_image is None and self.image is not None:
                self._pre_clipped_image = self.image.copy()

            self._image_clip = rect
            if self.image is not None:
                self.image.fill(pygame.Color('#00000000'))
                basic_blit(self.image, self._pre_clipped_image, self._image_clip, self._image_clip)

        elif self._image_clip is not None:
            self._image_clip = None
            self.set_image(self._pre_clipped_image)
        else:
            self._image_clip = None
示例#6
0
    def _find_spot_in_lt_cache(self, cache_surface, new_item, string_id):
        """
        Find a place in a long term cache surface for our new item from the short term cache.

        :param cache_surface: the surface to search.
        :param new_item: the item to cache.
        :param string_id: the look up id.
        :return: A tuple of the new rect we are reserving, and the rectangle it's inside of.
        """
        found_rectangle_cache = None
        found_rectangle_to_split = None
        current_surface = cache_surface['surface']
        surface_size = new_item[0].get_size()
        for free_rectangle in cache_surface['free_space_rectangles']:
            if (free_rectangle.width >= surface_size[0]
                    and free_rectangle.height >= surface_size[1]):
                # we fits, so we sits
                found_rectangle_to_split = free_rectangle
                found_rectangle_cache = pygame.Rect(free_rectangle.topleft,
                                                    surface_size)
                basic_blit(current_surface, new_item[0],
                           free_rectangle.topleft)
                self.cache_long_term_lookup[string_id] = {
                    'surface':
                    current_surface.subsurface(found_rectangle_cache),
                    'current_uses': new_item[1],
                    'total_uses': new_item[1]
                }
                break
        return found_rectangle_cache, found_rectangle_to_split
示例#7
0
    def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                               Tuple[int, int], Tuple[float,
                                                                      float]]):
        """
        Changes the size of the rounded rectangle shape. Relatively expensive to completely do so
        has support for 'temporary rapid resizing' while the dimensions are being changed
        frequently.

        :param dimensions: The new dimensions.

        """
        if (dimensions[0] == self.containing_rect.width
                and dimensions[1] == self.containing_rect.height):
            return False
        self.containing_rect.width = int(dimensions[0])
        self.containing_rect.height = int(dimensions[1])
        self.click_area_shape.width = int(
            dimensions[0]) - (2 * self.shadow_width)
        self.click_area_shape.height = int(
            dimensions[1]) - (2 * self.shadow_width)

        if self.shadow_width > 0:
            quick_surf = self.ui_manager.get_shadow(
                self.containing_rect.size,
                self.shadow_width,
                'rectangle',
                corner_radius=self.shadow_width + 2)
        else:
            quick_surf = pygame.surface.Surface(self.containing_rect.size,
                                                flags=pygame.SRCALPHA,
                                                depth=32)
            quick_surf.fill(pygame.Color('#00000000'))
        if isinstance(self.theming['normal_bg'], ColourGradient):
            grad_surf = pygame.surface.Surface(self.click_area_shape.size,
                                               flags=pygame.SRCALPHA,
                                               depth=32)
            grad_surf.fill(pygame.Color('#FFFFFFFF'))
            self.theming['normal_bg'].apply_gradient_to_surface(grad_surf)

            basic_blit(
                quick_surf, grad_surf,
                pygame.Rect((self.shadow_width, self.shadow_width),
                            self.click_area_shape.size))
        else:
            quick_surf.fill(
                self.theming['normal_bg'],
                pygame.Rect((self.shadow_width, self.shadow_width),
                            self.click_area_shape.size))

        self.states['normal'].surface = quick_surf
        self.finalise_images_and_text('normal_image', 'normal', 'normal_text',
                                      'normal_text_shadow', True)
        self.states['normal'].has_fresh_surface = True

        self.has_been_resized = True
        self.should_trigger_full_rebuild = True
        self.full_rebuild_countdown = self.time_until_full_rebuild_after_changing_size

        return True
    def blit_finalised_text_to_surf(self, surface: Surface):
        """
        Lets us blit a finalised text surface to an arbitrary surface.
        Useful for doing stuff with text effects.

        :param surface: the target surface to blit onto.
        """
        if self.finalised_surface is not None:
            basic_blit(surface, self.finalised_surface, (0, 0))
示例#9
0
 def apply_active_text_changes(self):
     """
     Updates the shape surface with any changes to the text surface. Useful when we've made
     small edits to the text surface
     """
     if self.text_box_layout is not None:
         for state_id, state in self.states.items():
             if state.pre_text_surface is not None and state.text_surface is not None:
                 state.surface = state.pre_text_surface.copy()
                 basic_blit(state.surface, state.text_surface, (0, 0))
示例#10
0
    def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                               Tuple[int, int], Tuple[float,
                                                                      float]]):
        """
        Method to directly set the dimensions of a text box.

        :param dimensions: The new dimensions to set.

        """
        self.relative_rect.width = int(dimensions[0])
        self.relative_rect.height = int(dimensions[1])
        self.rect.size = self.relative_rect.size

        if dimensions[0] >= 0 and dimensions[1] >= 0:
            if self.relative_right_margin is not None:
                self.relative_right_margin = self.ui_container.rect.right - self.rect.right

            if self.relative_bottom_margin is not None:
                self.relative_bottom_margin = self.ui_container.rect.bottom - self.rect.bottom

            self._update_container_clip()

            # Quick and dirty temporary scaling to cut down on number of
            # full rebuilds triggered when rapid scaling
            if self.image is not None:
                if (self.full_rebuild_countdown > 0.0
                        and (self.relative_rect.width > 0
                             and self.relative_rect.height > 0)):
                    new_image = pygame.surface.Surface(self.relative_rect.size,
                                                       flags=pygame.SRCALPHA,
                                                       depth=32)
                    new_image.fill(pygame.Color('#00000000'))
                    basic_blit(new_image, self.image, (0, 0))
                    self.set_image(new_image)

                    if self.scroll_bar is not None:
                        self.scroll_bar.set_dimensions(
                            (self.scroll_bar.relative_rect.width,
                             self.relative_rect.height -
                             (2 * self.border_width) -
                             (2 * self.shadow_width)))
                        scroll_bar_position = (self.relative_rect.right -
                                               self.border_width -
                                               self.shadow_width -
                                               self.scroll_bar_width,
                                               self.relative_rect.top +
                                               self.border_width +
                                               self.shadow_width)
                        self.scroll_bar.set_relative_position(
                            scroll_bar_position)

                self.should_trigger_full_rebuild = True
                self.full_rebuild_countdown = self.time_until_full_rebuild_after_changing_size
示例#11
0
 def _rebuild_shadow(self, new_image, text_render_rect):
     shadow_text_render = render_white_text_alpha_black_bg(self.font, self.text)
     apply_colour_to_surface(self.text_shadow_colour, shadow_text_render)
     for y_pos in range(-self.text_shadow_size, self.text_shadow_size + 1):
         shadow_text_rect = pygame.Rect((text_render_rect.x + self.text_shadow_offset[0],
                                         text_render_rect.y + self.text_shadow_offset[1]
                                         + y_pos),
                                        text_render_rect.size)
         basic_blit(new_image, shadow_text_render, shadow_text_rect)
     for x_pos in range(-self.text_shadow_size, self.text_shadow_size + 1):
         shadow_text_rect = pygame.Rect((text_render_rect.x + self.text_shadow_offset[0]
                                         + x_pos,
                                         text_render_rect.y + self.text_shadow_offset[1]),
                                        text_render_rect.size)
         basic_blit(new_image, shadow_text_render, shadow_text_rect)
     for x_and_y in range(-self.text_shadow_size, self.text_shadow_size + 1):
         shadow_text_rect = pygame.Rect(
             (text_render_rect.x + self.text_shadow_offset[0] + x_and_y,
              text_render_rect.y + self.text_shadow_offset[1] + x_and_y),
             text_render_rect.size)
         basic_blit(new_image, shadow_text_render, shadow_text_rect)
     for x_and_y in range(-self.text_shadow_size, self.text_shadow_size + 1):
         shadow_text_rect = pygame.Rect(
             (text_render_rect.x + self.text_shadow_offset[0] - x_and_y,
              text_render_rect.y + self.text_shadow_offset[1] + x_and_y),
             text_render_rect.size)
         basic_blit(new_image, shadow_text_render, shadow_text_rect)
    def redraw(self):
        """
        Redraws the entire text entry element onto the underlying sprite image. Usually called
        when the displayed text has been edited or changed in some fashion.
        """
        if self.is_enabled:
            if isinstance(self.background_colour, ColourGradient):
                self.text_image.fill(pygame.Color("#FFFFFFFF"))
                self.background_colour.apply_gradient_to_surface(
                    self.text_image)
            else:
                self.text_image.fill(self.background_colour)
        else:
            if isinstance(self.disabled_background_colour, ColourGradient):
                self.text_image.fill(pygame.Color("#FFFFFFFF"))
                self.disabled_background_colour.apply_gradient_to_surface(
                    self.text_image)
            else:
                self.text_image.fill(self.disabled_background_colour)

        if self.select_range[0] == self.select_range[1]:
            self._redraw_unselected_text()
        else:
            self._redraw_selected_text()

        text_clip_width = (self.rect.width - (self.padding[0] * 2) -
                           (self.shape_corner_radius * 2) -
                           (self.border_width * 2) - (self.shadow_width * 2))

        width_to_edit_pos = self.font.size(self.text[:self.edit_position])[0]

        if self.start_text_offset > width_to_edit_pos:
            self.start_text_offset = width_to_edit_pos
        elif width_to_edit_pos > (self.start_text_offset + text_clip_width):
            self.start_text_offset = max(0,
                                         width_to_edit_pos - text_clip_width)
        elif width_to_edit_pos == 0:
            self.start_text_offset = 0

        if len(self.text) > 0:
            basic_blit(
                self.text_image, self.text_surface, self.padding,
                pygame.Rect(
                    (self.start_text_offset, 0),
                    (text_clip_width,
                     (self.rect.height - (self.padding[1] * 2) -
                      (self.border_width * 2) - (self.shadow_width * 2)))))

        self.redraw_cursor()
示例#13
0
    def set_visual_debug_mode(self, activate_mode: bool):
        """
        Enables a debug mode for the element which displays layer information on top of it in
        a tiny font.

        :param activate_mode: True or False to enable or disable the mode.

        """
        if activate_mode:
            default_font = self.ui_manager.get_theme().get_font_dictionary(
            ).get_default_font()
            layer_text_render = render_white_text_alpha_black_bg(
                default_font, "UI Layer: " + str(self._layer))

            if self.image is not None:
                self.pre_debug_image = self.image.copy()
                # check if our surface is big enough to hold the debug info,
                # if not make a new, bigger copy
                make_new_larger_surface = False
                surf_width = self.image.get_width()
                surf_height = self.image.get_height()
                if self.image.get_width() < layer_text_render.get_width():
                    make_new_larger_surface = True
                    surf_width = layer_text_render.get_width()
                if self.image.get_height() < layer_text_render.get_height():
                    make_new_larger_surface = True
                    surf_height = layer_text_render.get_height()

                if make_new_larger_surface:
                    new_surface = pygame.surface.Surface(
                        (surf_width, surf_height),
                        flags=pygame.SRCALPHA,
                        depth=32)
                    basic_blit(new_surface, self.image, (0, 0))
                    self.set_image(new_surface)
                basic_blit(self.image, layer_text_render, (0, 0))
            else:
                self.set_image(layer_text_render)
            self._visual_debug_mode = True
        else:
            self.rebuild()
            self._visual_debug_mode = False
示例#14
0
    def finalise_text(self,
                      state_str,
                      text_colour_state_str: str = "",
                      text_shadow_colour_state_str: str = "",
                      only_text_changed: bool = False):
        """
        Finalise the text to a surface with some last-minute data that doesn't require the text
        be re-laid out.

        :param only_text_changed:
        :param state_str: The name of the shape's state we are finalising.
        :param text_colour_state_str: The string identifying the text colour to use.
        :param text_shadow_colour_state_str: The string identifying the text shadow
                                             colour to use.
        """
        if self.text_box_layout is not None:
            # copy the pre-text surface & create a new empty text surface for this state
            self.states[state_str].pre_text_surface = self.states[
                state_str].surface.copy()
            self.states[state_str].text_surface = pygame.surface.Surface(
                self.states[state_str].surface.get_size(),
                flags=pygame.SRCALPHA,
                depth=32)
            self.states[state_str].text_surface.fill('#00000000')

            if only_text_changed:

                self.text_box_layout.blit_finalised_text_to_surf(
                    self.states[state_str].text_surface)
                basic_blit(self.states[state_str].surface,
                           self.states[state_str].text_surface, (0, 0))
            else:
                self.text_box_layout.set_default_text_colour(
                    self.theming[text_colour_state_str])
                self.text_box_layout.set_default_text_shadow_colour(
                    self.theming[text_shadow_colour_state_str])
                self.text_box_layout.finalise_to_surf(
                    self.states[state_str].text_surface)
                basic_blit(self.states[state_str].surface,
                           self.states[state_str].text_surface, (0, 0))
    def redraw_cursor(self):
        """
        Redraws only the blinking edit cursor. This allows us to blink the cursor on and off
        without spending time redrawing all the text.
        """
        new_image = self.background_and_border.copy()
        basic_blit(new_image, self.text_image, self.text_image_rect)
        if self.cursor_on and self.is_enabled:
            cursor_len_str = self.text[:self.edit_position]
            cursor_size = self.font.size(cursor_len_str)
            self.cursor.x = (cursor_size[0] + self.text_image_rect.x +
                             self.padding[0] - self.start_text_offset)

            if not isinstance(self.text_colour, ColourGradient):
                pygame.draw.rect(new_image, self.text_colour, self.cursor)
            else:
                cursor_surface = pygame.surface.Surface(self.cursor.size,
                                                        flags=pygame.SRCALPHA,
                                                        depth=32)
                cursor_surface.fill(pygame.Color('#FFFFFFFF'))
                self.text_colour.apply_gradient_to_surface(cursor_surface)
                basic_blit(new_image, cursor_surface, self.cursor)

        self.set_image(new_image)
示例#16
0
    def update(self, time_delta: float):
        """
        Called once every update loop of the UI Manager. Used to react to scroll bar movement
        (if there is one), update the text effect (if there is one) and check if we are hovering
        over any text links (if there are any).

        :param time_delta: The time in seconds between calls to update. Useful for timing things.

        """
        super().update(time_delta)
        if not self.alive():
            return
        if self.scroll_bar is not None and self.scroll_bar.check_has_moved_recently(
        ):
            height_adjustment = int(self.scroll_bar.start_percentage *
                                    self.text_box_layout.layout_rect.height)

            drawable_area_size = (max(
                1, (self.rect[2] - (self.padding[0] * 2) -
                    (self.border_width * 2) - (self.shadow_width * 2) -
                    (2 * self.rounded_corner_offset))),
                                  max(1,
                                      (self.rect[3] - (self.padding[1] * 2) -
                                       (self.border_width * 2) -
                                       (self.shadow_width * 2) -
                                       (2 * self.rounded_corner_offset))))
            drawable_area = pygame.Rect((0, height_adjustment),
                                        drawable_area_size)

            if self.rect.width <= 0 or self.rect.height <= 0:
                return

            new_image = pygame.surface.Surface(self.rect.size,
                                               flags=pygame.SRCALPHA,
                                               depth=32)
            new_image.fill(pygame.Color(0, 0, 0, 0))
            basic_blit(new_image, self.background_surf, (0, 0))
            basic_blit(new_image, self.text_box_layout.finalised_surface,
                       (self.padding[0] + self.border_width +
                        self.shadow_width + self.rounded_corner_offset,
                        self.padding[1] + self.border_width +
                        self.shadow_width + self.rounded_corner_offset),
                       drawable_area)
            self.set_image(new_image)

        mouse_x, mouse_y = self.ui_manager.get_mouse_position()
        should_redraw_from_layout = False

        if self.scroll_bar is not None:
            height_adjustment = (self.scroll_bar.start_percentage *
                                 self.text_box_layout.layout_rect.height)
        else:
            height_adjustment = 0
        base_x = int(self.rect[0] + self.padding[0] + self.border_width +
                     self.shadow_width + self.rounded_corner_offset)
        base_y = int(self.rect[1] + self.padding[1] + self.border_width +
                     self.shadow_width + self.rounded_corner_offset -
                     height_adjustment)

        for chunk in self.link_hover_chunks:
            hovered_currently = False

            hover_rect = pygame.Rect((base_x + chunk.x, base_y + chunk.y),
                                     chunk.size)
            if hover_rect.collidepoint(mouse_x,
                                       mouse_y) and self.rect.collidepoint(
                                           mouse_x, mouse_y):
                hovered_currently = True
            if chunk.is_hovered and not hovered_currently:
                chunk.on_unhovered()
                should_redraw_from_layout = True
            elif hovered_currently and not chunk.is_hovered:
                chunk.on_hovered()
                should_redraw_from_layout = True

        if should_redraw_from_layout:
            self.redraw_from_text_block()

        self.update_text_effect(time_delta)

        if self.should_trigger_full_rebuild and self.full_rebuild_countdown <= 0.0:
            self.rebuild()

        if self.full_rebuild_countdown > 0.0:
            self.full_rebuild_countdown -= time_delta
示例#17
0
    def rebuild(self):
        """
        Rebuild whatever needs building.

        """
        if self.scroll_bar is not None:
            self.scroll_bar.kill()

        # The text_wrap_area is the part of the text box that we try to keep the text inside
        # of so that none  of it overlaps. Essentially we start with the containing box,
        # subtract the border, then subtract the padding, then if necessary subtract the width
        # of the scroll bar
        self.rounded_corner_offset = int(self.shape_corner_radius -
                                         (math.sin(math.pi / 4) *
                                          self.shape_corner_radius))
        self.text_wrap_rect = pygame.Rect(
            (self.rect[0] + self.padding[0] + self.border_width +
             self.shadow_width + self.rounded_corner_offset),
            (self.rect[1] + self.padding[1] + self.border_width +
             self.shadow_width + self.rounded_corner_offset),
            max(1, (self.rect[2] - (self.padding[0] * 2) -
                    (self.border_width * 2) - (self.shadow_width * 2) -
                    (2 * self.rounded_corner_offset))),
            max(1, (self.rect[3] - (self.padding[1] * 2) -
                    (self.border_width * 2) - (self.shadow_width * 2) -
                    (2 * self.rounded_corner_offset))))
        if self.wrap_to_height or self.rect[3] == -1:
            self.text_wrap_rect.height = -1
        if self.rect[2] == -1:
            self.text_wrap_rect.width = -1

        drawable_area_size = (self.text_wrap_rect[2], self.text_wrap_rect[3])

        # This gives us the height of the text at the 'width' of the text_wrap_area
        self.parse_html_into_style_data()
        if self.text_box_layout is not None:
            if self.wrap_to_height or self.rect[3] == -1 or self.rect[2] == -1:
                final_text_area_size = self.text_box_layout.layout_rect.size
                new_dimensions = (
                    (final_text_area_size[0] + (self.padding[0] * 2) +
                     (self.border_width * 2) + (self.shadow_width * 2) +
                     (2 * self.rounded_corner_offset)),
                    (final_text_area_size[1] + (self.padding[1] * 2) +
                     (self.border_width * 2) + (self.shadow_width * 2) +
                     (2 * self.rounded_corner_offset)))
                self.set_dimensions(new_dimensions)

                # need to regen this because it was dynamically generated
                drawable_area_size = (max(
                    1, (self.rect[2] - (self.padding[0] * 2) -
                        (self.border_width * 2) - (self.shadow_width * 2) -
                        (2 * self.rounded_corner_offset))),
                                      max(1,
                                          (self.rect[3] -
                                           (self.padding[1] * 2) -
                                           (self.border_width * 2) -
                                           (self.shadow_width * 2) -
                                           (2 * self.rounded_corner_offset))))

            elif self.text_box_layout.layout_rect.height > self.text_wrap_rect[
                    3]:
                # We need a scrollbar because our text is longer than the space we
                # have to display it. This also means we need to parse the text again.
                text_rect_width = (self.rect[2] - (self.padding[0] * 2) -
                                   (self.border_width * 2) -
                                   (self.shadow_width * 2) -
                                   self.rounded_corner_offset -
                                   self.scroll_bar_width)
                self.text_wrap_rect = pygame.Rect(
                    (self.rect[0] + self.padding[0] + self.border_width +
                     self.shadow_width + self.rounded_corner_offset),
                    (self.rect[1] + self.padding[1] + self.border_width +
                     self.shadow_width + self.rounded_corner_offset),
                    max(1, text_rect_width),
                    max(1, (self.rect[3] - (self.padding[1] * 2) -
                            (self.border_width * 2) - (self.shadow_width * 2) -
                            (2 * self.rounded_corner_offset))))
                self.parse_html_into_style_data()
                percentage_visible = (self.text_wrap_rect[3] /
                                      self.text_box_layout.layout_rect.height)
                scroll_bar_position = (self.relative_rect.right -
                                       self.border_width - self.shadow_width -
                                       self.scroll_bar_width,
                                       self.relative_rect.top +
                                       self.border_width + self.shadow_width)

                scroll_bar_rect = pygame.Rect(
                    scroll_bar_position,
                    (self.scroll_bar_width, self.rect.height -
                     (2 * self.border_width) - (2 * self.shadow_width)))
                self.scroll_bar = UIVerticalScrollBar(scroll_bar_rect,
                                                      percentage_visible,
                                                      self.ui_manager,
                                                      self.ui_container,
                                                      parent_element=self,
                                                      visible=self.visible)
                self.join_focus_sets(self.scroll_bar)
            else:
                new_dimensions = (self.rect[2], self.rect[3])
                self.set_dimensions(new_dimensions)

        theming_parameters = {
            'normal_bg': self.background_colour,
            'normal_border': self.border_colour,
            'border_width': self.border_width,
            'shadow_width': self.shadow_width,
            'shape_corner_radius': self.shape_corner_radius
        }

        if self.shape == 'rectangle':
            self.drawable_shape = RectDrawableShape(self.rect,
                                                    theming_parameters,
                                                    ['normal'],
                                                    self.ui_manager)
        elif self.shape == 'rounded_rectangle':
            self.drawable_shape = RoundedRectangleShape(
                self.rect, theming_parameters, ['normal'], self.ui_manager)

        self.background_surf = self.drawable_shape.get_fresh_surface()

        if self.scroll_bar is not None:
            height_adjustment = int(self.scroll_bar.start_percentage *
                                    self.text_box_layout.layout_rect.height)
        else:
            height_adjustment = 0

        if self.rect.width <= 0 or self.rect.height <= 0:
            return

        drawable_area = pygame.Rect((0, height_adjustment), drawable_area_size)
        new_image = pygame.surface.Surface(self.rect.size,
                                           flags=pygame.SRCALPHA,
                                           depth=32)
        new_image.fill(pygame.Color(0, 0, 0, 0))
        basic_blit(new_image, self.background_surf, (0, 0))

        basic_blit(
            new_image, self.text_box_layout.finalised_surface,
            (self.padding[0] + self.border_width + self.shadow_width +
             self.rounded_corner_offset, self.padding[1] + self.border_width +
             self.shadow_width + self.rounded_corner_offset), drawable_area)

        self.set_image(new_image)
        self.link_hover_chunks = []
        self.text_box_layout.add_chunks_to_hover_group(self.link_hover_chunks)

        self.should_trigger_full_rebuild = False
        self.full_rebuild_countdown = self.time_until_full_rebuild_after_changing_size
示例#18
0
    def rebuild(self):
        """
        Re-render the text to the label's underlying sprite image. This allows us to change what
        the displayed text is or remake it with different theming (if the theming has changed).
        """

        text_size = self.font.size(self.text)
        if text_size[1] > self.relative_rect.height or text_size[0] > self.relative_rect.width:
            width_overlap = self.relative_rect.width - text_size[0]
            height_overlap = self.relative_rect.height - text_size[1]
            warn_text = ('Label Rect is too small for text: '
                         '' + self.text + ' - size diff: ' + str((width_overlap, height_overlap)))
            warnings.warn(warn_text, UserWarning)

        new_image = pygame.surface.Surface(self.relative_rect.size,
                                           flags=pygame.SRCALPHA,
                                           depth=32)

        if isinstance(self.bg_colour, ColourGradient):
            new_image.fill(pygame.Color('#FFFFFFFF'))
            self.bg_colour.apply_gradient_to_surface(new_image)
            text_render = render_white_text_alpha_black_bg(self.font, self.text)
            if self.is_enabled:
                if isinstance(self.text_colour, ColourGradient):
                    self.text_colour.apply_gradient_to_surface(text_render)
                else:
                    apply_colour_to_surface(self.text_colour, text_render)
            else:
                if isinstance(self.disabled_text_colour, ColourGradient):
                    self.disabled_text_colour.apply_gradient_to_surface(text_render)
                else:
                    apply_colour_to_surface(self.disabled_text_colour, text_render)
        else:
            new_image.fill(self.bg_colour)
            if self.is_enabled:
                if isinstance(self.text_colour, ColourGradient):
                    text_render = render_white_text_alpha_black_bg(self.font, self.text)
                    self.text_colour.apply_gradient_to_surface(text_render)
                else:
                    if self.bg_colour.a != 255 or self.text_shadow:
                        text_render = render_white_text_alpha_black_bg(self.font, self.text)
                        apply_colour_to_surface(self.text_colour, text_render)
                    else:
                        text_render = self.font.render(self.text, True,
                                                       self.text_colour, self.bg_colour)
                        text_render = text_render.convert_alpha()
            else:
                if isinstance(self.disabled_text_colour, ColourGradient):
                    text_render = render_white_text_alpha_black_bg(self.font, self.text)
                    self.disabled_text_colour.apply_gradient_to_surface(text_render)
                else:
                    if self.bg_colour.a != 255 or self.text_shadow:
                        text_render = render_white_text_alpha_black_bg(self.font, self.text)
                        apply_colour_to_surface(self.disabled_text_colour, text_render)
                    else:
                        text_render = self.font.render(self.text, True,
                                                       self.disabled_text_colour, self.bg_colour)
                        text_render = text_render.convert_alpha()
        text_render_rect = text_render.get_rect(centerx=int(self.rect.width / 2),
                                                centery=int(self.rect.height / 2))

        if self.text_shadow:
            self._rebuild_shadow(new_image, text_render_rect)

        basic_blit(new_image, text_render, text_render_rect)

        self.set_image(new_image)
示例#19
0
    def _draw_chunks_to_surface(
            self, lines_of_chunks: List[Dict[str, Union[List[Dict[str, Any]],
                                                        int]]]):
        """
        Takes a list of lines of chunks and draws it to a surface using the styles and positions
        attached to the chunks.

        :param lines_of_chunks:
        :return:
        """
        self.block_sprite = None
        if self.height != -1 and self.width != -1:
            self.block_sprite = pygame.surface.Surface(
                (self.width, self.height), pygame.SRCALPHA, depth=32)
            self.block_sprite.fill(pygame.Color('#00000000'))
        position = [0, 0]
        line_height_acc = 0
        max_line_length = 0
        for line in lines_of_chunks:
            line_chunks = []
            max_line_char_height = 0
            for chunk in line['chunks']:
                if len(chunk['text']) > 0:
                    new_chunk = StyledChunk(
                        chunk['style'].font_size, chunk['style'].font_name,
                        chunk['text'], chunk['style'].style,
                        chunk['style'].colour, chunk['style'].bg_colour,
                        chunk['style'].is_link, chunk['style'].link_href,
                        self.link_style, (position[0], position[1]),
                        self.font_dict)
                    position[0] += new_chunk.advance
                    if new_chunk.height > max_line_char_height:
                        max_line_char_height = new_chunk.height
                    line_chunks.append(new_chunk)

                    if self.block_sprite is not None:
                        # need to adjust y start pos based on ascents
                        new_chunk.rect.y += (line['line_ascent'] -
                                             new_chunk.ascent)
                        basic_blit(self.block_sprite, new_chunk.rendered_chunk,
                                   new_chunk.rect)

            text_line = TextBlock.TextLine()
            text_line.chunks = line_chunks
            text_line.max_line_ascent = line['line_ascent']
            self.lines.append(text_line)

            if position[0] > max_line_length:
                max_line_length = position[0]
            position[0] = 0
            position[1] += max_line_char_height
            line_height_acc += max_line_char_height
        if self.block_sprite is None:
            self.width = max_line_length if self.width == -1 else self.width
            self.height = line_height_acc if self.height == -1 else self.height
            self.block_sprite = pygame.surface.Surface(
                (self.width, self.height), pygame.SRCALPHA, depth=32)
            self.block_sprite.fill(pygame.Color('#00000000'))

            for line in self.lines:
                for chunk in line.chunks:
                    # need to adjust y start pos based on ascents
                    chunk.rect.y += line.max_line_ascent - chunk.ascent
                    basic_blit(self.block_sprite, chunk.rendered_chunk,
                               chunk.rect)

        self.final_dimensions = (self.width, self.height)
示例#20
0
    def rebuild_images_and_text(self, image_state_str: str, state_str: str,
                                text_colour_state_str: str):
        """
        Rebuilds any text or image used by a specific state in the drawable shape. Effectively
        this means adding them on top of whatever is already in the state's surface. As such it
        should generally be called last in the process of building up a finished drawable shape
        state.

        :param image_state_str: image ID of the state we are going to be adding images and text to.
        :param state_str: normal ID of the state we are going to be adding images and text to.
        :param text_colour_state_str: text ID of the state we are going to be adding images and
                                      text to.

        """
        # Draw any themed images
        if image_state_str in self.theming and self.theming[
                image_state_str] is not None:
            image_rect = self.theming[image_state_str].get_rect()
            image_rect.center = (int(self.containing_rect.width / 2),
                                 int(self.containing_rect.height / 2))
            basic_blit(self.states[state_str].surface,
                       self.theming[image_state_str], image_rect)
        # Draw any text
        if 'text' in self.theming and 'font' in self.theming and self.theming[
                'text'] is not None:
            if len(self.theming['text']
                   ) > 0 and text_colour_state_str in self.theming:
                text_surface = render_white_text_alpha_black_bg(
                    font=self.theming['font'], text=self.theming['text'])
                if isinstance(self.theming[text_colour_state_str],
                              ColourGradient):
                    self.theming[
                        text_colour_state_str].apply_gradient_to_surface(
                            text_surface)
                else:
                    apply_colour_to_surface(
                        self.theming[text_colour_state_str], text_surface)
            else:
                text_surface = None

            if 'text_shadow' in self.theming:
                text_shadow = render_white_text_alpha_black_bg(
                    font=self.theming['font'], text=self.theming['text'])
                apply_colour_to_surface(self.theming['text_shadow'],
                                        text_shadow)

                basic_blit(
                    self.states[state_str].surface, text_shadow,
                    (self.aligned_text_rect.x, self.aligned_text_rect.y + 1))
                basic_blit(
                    self.states[state_str].surface, text_shadow,
                    (self.aligned_text_rect.x, self.aligned_text_rect.y - 1))
                basic_blit(
                    self.states[state_str].surface, text_shadow,
                    (self.aligned_text_rect.x + 1, self.aligned_text_rect.y))
                basic_blit(
                    self.states[state_str].surface, text_shadow,
                    (self.aligned_text_rect.x - 1, self.aligned_text_rect.y))

            if text_surface is not None and self.aligned_text_rect is not None:
                basic_blit(self.states[state_str].surface, text_surface,
                           self.aligned_text_rect)
    def redraw_state(self, state_str: str):
        """
        Redraws the shape's surface for a given UI state.

        :param state_str: The ID string of the state to rebuild.

        """
        text_colour_state_str = state_str + '_text'
        image_state_str = state_str + '_image'
        bg_col = self.theming[state_str + '_bg']
        border_col = self.theming[state_str + '_border']

        found_shape = None
        shape_id = None
        if 'filled_bar' not in self.theming and 'filled_bar_width_percentage' not in self.theming:
            shape_id = self.shape_cache.build_cache_id('rounded_rectangle',
                                                       self.containing_rect.size,
                                                       self.shadow_width,
                                                       self.border_width,
                                                       border_col,
                                                       bg_col,
                                                       self.corner_radius)

            found_shape = self.shape_cache.find_surface_in_cache(shape_id)
        if found_shape is not None:
            self.states[state_str].surface = found_shape.copy()
        else:
            border_corner_radius = self.corner_radius

            self.states[state_str].surface = self.base_surface.copy()

            # Try one AA call method
            aa_amount = 4
            self.border_rect = pygame.Rect((self.shadow_width * aa_amount,
                                            self.shadow_width * aa_amount),
                                           (self.click_area_shape.width * aa_amount,
                                            self.click_area_shape.height * aa_amount))

            self.background_rect = pygame.Rect(((self.border_width +
                                                 self.shadow_width) * aa_amount,
                                                (self.border_width +
                                                 self.shadow_width) * aa_amount),
                                               (self.border_rect.width -
                                                (2 * self.border_width * aa_amount),
                                                self.border_rect.height -
                                                (2 * self.border_width * aa_amount)))

            dimension_scale = min(self.background_rect.width / max(self.border_rect.width, 1),
                                  self.background_rect.height / max(self.border_rect.height, 1))
            bg_corner_radius = int(border_corner_radius * dimension_scale)

            bab_surface = pygame.surface.Surface((self.containing_rect.width * aa_amount,
                                                  self.containing_rect.height * aa_amount),
                                                 flags=pygame.SRCALPHA, depth=32)
            bab_surface.fill(pygame.Color('#00000000'))
            if self.border_width > 0:
                shape_surface = self.clear_and_create_shape_surface(bab_surface,
                                                                    self.border_rect,
                                                                    0,
                                                                    border_corner_radius,
                                                                    aa_amount=aa_amount,
                                                                    clear=False)
                if isinstance(border_col, ColourGradient):
                    border_col.apply_gradient_to_surface(shape_surface)
                else:
                    apply_colour_to_surface(border_col, shape_surface)

                basic_blit(bab_surface, shape_surface, self.border_rect)

            shape_surface = self.clear_and_create_shape_surface(bab_surface,
                                                                self.background_rect,
                                                                0,
                                                                bg_corner_radius,
                                                                aa_amount=aa_amount)

            if 'filled_bar' in self.theming and 'filled_bar_width_percentage' in self.theming:
                self._redraw_filled_bar(bg_col, shape_surface)
            else:
                if isinstance(bg_col, ColourGradient):
                    bg_col.apply_gradient_to_surface(shape_surface)
                else:
                    apply_colour_to_surface(bg_col, shape_surface)

            basic_blit(bab_surface, shape_surface, self.background_rect)

            # apply AA to background
            bab_surface = pygame.transform.smoothscale(bab_surface, self.containing_rect.size)

            basic_blit(self.states[state_str].surface, bab_surface, (0, 0))

            if self.states[state_str].cached_background_id is not None:
                cached_id = self.states[state_str].cached_background_id
                self.shape_cache.remove_user_from_cache_item(cached_id)
            if (not self.has_been_resized
                    and ((self.containing_rect.width * self.containing_rect.height) < 40000)
                    and (shape_id is not None
                         and self.states[state_str].surface.get_width() <= 1024
                         and self.states[state_str].surface.get_height() <= 1024)):
                self.shape_cache.add_surface_to_cache(self.states[state_str].surface.copy(),
                                                      shape_id)
                self.states[state_str].cached_background_id = shape_id

        self.rebuild_images_and_text(image_state_str, state_str, text_colour_state_str)

        self.states[state_str].has_fresh_surface = True
        self.states[state_str].generated = True
    def redraw_state(self, state_str: str):
        """
        Redraws the shape's surface for a given UI state.

        :param state_str: The ID string of the state to rebuild.

        """
        border_colour_state_str = state_str + '_border'
        bg_colour_state_str = state_str + '_bg'
        text_colour_state_str = state_str + '_text'
        image_state_str = state_str + '_image'

        found_shape = None
        shape_id = None
        if 'filled_bar' not in self.theming and 'filled_bar_width_percentage' not in self.theming:
            shape_id = self.shape_cache.build_cache_id(
                'ellipse', self.containing_rect.size, self.shadow_width,
                self.border_width, self.theming[border_colour_state_str],
                self.theming[bg_colour_state_str])

            found_shape = self.shape_cache.find_surface_in_cache(shape_id)
        if found_shape is not None:
            self.states[state_str].surface = found_shape.copy()
        else:
            self.states[state_str].surface = self.base_surface.copy()

            # Try one AA call method
            aa_amount = 4
            self.border_rect = pygame.Rect(
                (self.shadow_width * aa_amount, self.shadow_width * aa_amount),
                (self.click_area_shape.width * aa_amount,
                 self.click_area_shape.height * aa_amount))

            self.background_rect = pygame.Rect(
                ((self.border_width + self.shadow_width) * aa_amount,
                 (self.border_width + self.shadow_width) * aa_amount),
                (self.border_rect.width -
                 (2 * self.border_width * aa_amount), self.border_rect.height -
                 (2 * self.border_width * aa_amount)))

            bab_surface = pygame.surface.Surface(
                (self.containing_rect.width * aa_amount,
                 self.containing_rect.height * aa_amount),
                flags=pygame.SRCALPHA,
                depth=32)
            bab_surface.fill(pygame.Color('#00000000'))
            if self.border_width > 0:
                if isinstance(self.theming[border_colour_state_str],
                              ColourGradient):
                    shape_surface = self.clear_and_create_shape_surface(
                        bab_surface,
                        self.border_rect,
                        0,
                        aa_amount=aa_amount,
                        clear=False)
                    self.theming[
                        border_colour_state_str].apply_gradient_to_surface(
                            shape_surface)
                else:
                    shape_surface = self.clear_and_create_shape_surface(
                        bab_surface,
                        self.border_rect,
                        0,
                        aa_amount=aa_amount,
                        clear=False)
                    apply_colour_to_surface(
                        self.theming[border_colour_state_str], shape_surface)
                basic_blit(bab_surface, shape_surface, self.border_rect)
            if isinstance(self.theming[bg_colour_state_str], ColourGradient):
                shape_surface = self.clear_and_create_shape_surface(
                    bab_surface, self.background_rect, 1, aa_amount=aa_amount)
                self.theming[bg_colour_state_str].apply_gradient_to_surface(
                    shape_surface)
            else:
                shape_surface = self.clear_and_create_shape_surface(
                    bab_surface, self.background_rect, 1, aa_amount=aa_amount)
                apply_colour_to_surface(self.theming[bg_colour_state_str],
                                        shape_surface)

            basic_blit(bab_surface, shape_surface, self.background_rect)
            # apply AA to background
            bab_surface = pygame.transform.smoothscale(
                bab_surface, self.containing_rect.size)

            # cut a hole in shadow, then blit background into it
            sub_surface = pygame.surface.Surface(
                ((self.containing_rect.width -
                  (2 * self.shadow_width)) * aa_amount,
                 (self.containing_rect.height -
                  (2 * self.shadow_width)) * aa_amount),
                flags=pygame.SRCALPHA,
                depth=32)
            sub_surface.fill(pygame.Color('#00000000'))
            pygame.draw.ellipse(sub_surface, pygame.Color("#FFFFFFFF"),
                                sub_surface.get_rect())
            small_sub = pygame.transform.smoothscale(
                sub_surface,
                (self.containing_rect.width - (2 * self.shadow_width),
                 self.containing_rect.height - (2 * self.shadow_width)))
            self.states[state_str].surface.blit(
                small_sub,
                pygame.Rect((self.shadow_width, self.shadow_width),
                            sub_surface.get_size()),
                special_flags=pygame.BLEND_RGBA_SUB)
            basic_blit(self.states[state_str].surface, bab_surface, (0, 0))

            if (shape_id is not None
                    and self.states[state_str].surface.get_width() <= 1024
                    and self.states[state_str].surface.get_height() <= 1024):
                self.shape_cache.add_surface_to_cache(
                    self.states[state_str].surface.copy(), shape_id)

        self.rebuild_images_and_text(image_state_str, state_str,
                                     text_colour_state_str)

        self.states[state_str].has_fresh_surface = True
        self.states[state_str].generated = True
    def redraw_state(self, state_str: str, add_text: bool = True):
        """
        Redraws the shape's surface for a given UI state.

        :param add_text:
        :param state_str: The ID string of the state to rebuild.

        """
        border_colour_state_str = state_str + '_border'
        bg_colour_state_str = state_str + '_bg'
        text_colour_state_str = state_str + '_text'
        text_shadow_colour_state_str = state_str + '_text_shadow'
        image_state_str = state_str + '_image'

        found_shape = None
        shape_id = None
        if 'filled_bar' not in self.theming and 'filled_bar_width_percentage' not in self.theming:
            shape_id = self.shape_cache.build_cache_id('rectangle', self.containing_rect.size,
                                                       self.shadow_width,
                                                       self.border_width,
                                                       self.theming[border_colour_state_str],
                                                       self.theming[bg_colour_state_str])

            found_shape = self.shape_cache.find_surface_in_cache(shape_id)
        if found_shape is not None:
            self.states[state_str].surface = found_shape.copy()
        else:
            self.states[state_str].surface = self.base_surface.copy()

            if self.border_width > 0:

                if isinstance(self.theming[border_colour_state_str], ColourGradient):
                    border_shape_surface = pygame.surface.Surface(self.border_rect.size,
                                                                  flags=pygame.SRCALPHA, depth=32)
                    border_shape_surface.fill(pygame.Color('#FFFFFFFF'))
                    self.states[state_str].surface.blit(border_shape_surface,
                                                        self.border_rect,
                                                        special_flags=pygame.BLEND_RGBA_SUB)
                    self.theming[border_colour_state_str].apply_gradient_to_surface(
                        border_shape_surface)
                    basic_blit(self.states[state_str].surface,
                               border_shape_surface, self.border_rect)
                else:
                    self.states[state_str].surface.fill(self.theming[border_colour_state_str],
                                                        self.border_rect)

            if isinstance(self.theming[bg_colour_state_str], ColourGradient):
                background_shape_surface = pygame.surface.Surface(self.background_rect.size,
                                                                  flags=pygame.SRCALPHA, depth=32)
                background_shape_surface.fill(pygame.Color('#FFFFFFFF'))
                self.states[state_str].surface.blit(background_shape_surface,
                                                    self.background_rect,
                                                    special_flags=pygame.BLEND_RGBA_SUB)
                self.theming[bg_colour_state_str].apply_gradient_to_surface(
                    background_shape_surface)
                basic_blit(self.states[state_str].surface,
                           background_shape_surface, self.background_rect)
            else:
                self.states[state_str].surface.fill(self.theming[bg_colour_state_str],
                                                    self.background_rect)

            if 'filled_bar' in self.theming and 'filled_bar_width_percentage' in self.theming:
                bar_rect = pygame.Rect(self.background_rect.topleft,
                                       (int(self.theming['filled_bar_width_percentage'] *
                                            self.background_rect.width),
                                        self.background_rect.height))
                if isinstance(self.theming['filled_bar'], ColourGradient):
                    bar_shape_surface = pygame.surface.Surface(bar_rect.size,
                                                               flags=pygame.SRCALPHA,
                                                               depth=32)
                    bar_shape_surface.fill(pygame.Color('#FFFFFFFF'))
                    self.states[state_str].surface.blit(bar_shape_surface, bar_rect,
                                                        special_flags=pygame.BLEND_RGBA_SUB)
                    self.theming['filled_bar'].apply_gradient_to_surface(bar_shape_surface)
                    basic_blit(self.states[state_str].surface,
                               bar_shape_surface, bar_rect)
                else:
                    self.states[state_str].surface.fill(self.theming['filled_bar'], bar_rect)

            if self.states[state_str].cached_background_id is not None:
                self.shape_cache.remove_user_from_cache_item(
                    self.states[state_str].cached_background_id)
            if (not self.has_been_resized
                    and ((self.containing_rect.width * self.containing_rect.height) < 40000)
                    and (shape_id is not None
                         and self.states[state_str].surface.get_width() <= 1024
                         and self.states[state_str].surface.get_height() <= 1024)):
                self.shape_cache.add_surface_to_cache(self.states[state_str].surface.copy(),
                                                      shape_id)
                self.states[state_str].cached_background_id = shape_id

        self.finalise_images_and_text(image_state_str, state_str,
                                      text_colour_state_str,
                                      text_shadow_colour_state_str,
                                      add_text)

        self.states[state_str].has_fresh_surface = True
        self.states[state_str].generated = True
    def _redraw_selected_text(self):
        """
        Redraw text where some has been selected by a user.
        """
        low_end = min(self.select_range[0], self.select_range[1])
        high_end = max(self.select_range[0], self.select_range[1])
        pre_select_area_text = self.text[:low_end]
        select_area_text = self.text[low_end:high_end]
        post_select_area_text = self.text[high_end:]
        pre_select_area_surface = None
        post_select_area_surface = None

        overall_size = self.font.size(self.text)
        advances = [
            letter_metrics[4]
            for letter_metrics in self.font.metrics(self.text)
        ]
        pre_select_width = sum(advances[:low_end])
        select_area_width = sum(advances[low_end:high_end])

        if len(pre_select_area_text) > 0:
            pre_select_area_surface = self._draw_text_with_grad_or_col(
                pre_select_area_text, self.text_colour)

        if isinstance(self.selected_bg_colour, ColourGradient):
            select_area_surface = pygame.surface.Surface(
                (select_area_width, overall_size[1]),
                flags=pygame.SRCALPHA,
                depth=32)
            select_area_surface.fill(pygame.Color('#FFFFFFFF'))
            self.selected_bg_colour.apply_gradient_to_surface(
                select_area_surface)

            alpha_text = self._draw_text_with_grad_or_col(
                select_area_text, self.selected_text_colour)

            basic_blit(select_area_surface, alpha_text, (0, 0))
        else:
            if isinstance(self.selected_text_colour, ColourGradient):
                select_area_surface = pygame.surface.Surface(
                    (select_area_width, overall_size[1]),
                    flags=pygame.SRCALPHA,
                    depth=32)
                select_area_surface.fill(self.selected_bg_colour)

                alpha_text = render_white_text_alpha_black_bg(
                    font=self.font, text=select_area_text)
                self.selected_text_colour.apply_gradient_to_surface(alpha_text)
                basic_blit(select_area_surface, alpha_text, (0, 0))

            else:
                select_area_surface = self.font.render(
                    select_area_text, True, self.selected_text_colour,
                    self.selected_bg_colour).convert_alpha()
        if len(post_select_area_text) > 0:
            post_select_area_surface = self._draw_text_with_grad_or_col(
                post_select_area_text, self.text_colour)

        self.text_surface = pygame.surface.Surface(overall_size,
                                                   flags=pygame.SRCALPHA,
                                                   depth=32)

        if isinstance(self.background_colour, ColourGradient):
            self.text_image.fill(pygame.Color("#FFFFFFFF"))
            self.background_colour.apply_gradient_to_surface(self.text_image)
        else:
            self.text_image.fill(self.background_colour)

        if pre_select_area_surface is not None:
            basic_blit(self.text_surface, pre_select_area_surface, (0, 0))

        basic_blit(self.text_surface, select_area_surface,
                   (pre_select_width, 0))

        if post_select_area_surface is not None:
            basic_blit(self.text_surface, post_select_area_surface,
                       (pre_select_width + select_area_width, 0))