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_filled_bar(self, bg_col: Union[pygame.Color, ColourGradient], shape_surface: pygame.surface.Surface): """ Draw a 'filled bar' onto our drawable shape, allows for things like loading bars, health bars etc. :param bg_col: the colour or gradient of the bar. :param shape_surface: the surface we are drawing on to. """ filled_bar_width = int(self.background_rect.width * self.theming['filled_bar_width_percentage']) bar_rect = pygame.Rect((0, 0), (filled_bar_width, self.background_rect.height)) unfilled_bar_width = self.background_rect.width - filled_bar_width unfilled_bar_rect = pygame.Rect((filled_bar_width, 0), (unfilled_bar_width, self.background_rect.height)) if isinstance(bg_col, ColourGradient): bg_col.apply_gradient_to_surface(shape_surface, unfilled_bar_rect) else: apply_colour_to_surface(bg_col, shape_surface, unfilled_bar_rect) if isinstance(self.theming['filled_bar'], ColourGradient): self.theming['filled_bar'].apply_gradient_to_surface(shape_surface, bar_rect) else: apply_colour_to_surface(self.theming['filled_bar'], shape_surface, bar_rect)
def test_apply_colour_to_surface(self, _init_pygame, default_ui_manager: UIManager, default_display_surface): DrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100), theming_parameters={}, states=['normal'], manager=default_ui_manager) test_surface = pygame.Surface((50, 50), flags=pygame.SRCALPHA, depth=32) test_surface.fill(pygame.Color(255, 255, 255, 255)) apply_colour_to_surface(pygame.Color(50, 100, 50, 255), test_surface) after_application_colour = test_surface.get_at((0, 0)) # multiply blend always appears to be 1 pixel down in every channel assert after_application_colour == pygame.Color(50-1, 100-1, 50-1, 255-1) test_surface_2 = pygame.Surface((50, 50), flags=pygame.SRCALPHA, depth=32) test_surface_2.fill(pygame.Color(255, 255, 255, 255)) apply_colour_to_surface(pygame.Color(150, 100, 150, 255), test_surface_2, pygame.Rect(0, 0, 25, 50)) after_application_colour = test_surface_2.get_at((0, 0)) # multiply blend always appears to be 1 pixel down in every channel assert after_application_colour == pygame.Color(150 - 1, 100 - 1, 150 - 1, 255 - 1) after_application_colour = test_surface_2.get_at((30, 0)) assert after_application_colour == pygame.Color(255, 255, 255, 255)
def redraw(self): """ Renders the 'chunk' text to the 'rendered_chunk' surface. """ if self.style.underline or (self.is_hovered and self.link_hover_underline) or \ (self.link_normal_underline and not self.is_hovered): self.font.set_underline(True) if len(self.chunk) > 0: if isinstance(self.colour, ColourGradient): self.rendered_chunk = render_white_text_alpha_black_bg(self.font, self.chunk) self.colour.apply_gradient_to_surface(self.rendered_chunk) else: if isinstance(self.bg_colour, ColourGradient) or self.bg_colour.a != 255: self.rendered_chunk = render_white_text_alpha_black_bg(self.font, self.chunk) apply_colour_to_surface(self.colour, self.rendered_chunk) else: self.rendered_chunk = self.font.render(self.chunk, True, self.colour, self.bg_colour).convert_alpha() else: self.rendered_chunk = pygame.surface.Surface((0, 0), flags=pygame.SRCALPHA, depth=32) self.font.set_underline(False) new_metrics = self.font.metrics(self.chunk) new_ascent = self.font.get_ascent() new_width = self.font.size(self.chunk)[0] new_height = self.font.size(self.chunk)[1] new_advance = sum(new_metrics[i][4] for i in range(len(self.chunk)) if len(new_metrics[i]) == 5) if (new_ascent == self.ascent and new_width == self.width and new_height == self.height and new_advance == self.advance): self.metrics_changed_after_redraw = False else: self.metrics_changed_after_redraw = True self.ascent = new_ascent self.width = new_width self.height = new_height self.advance = new_advance self.rect = pygame.Rect(self.position, (self.width, self.height))
def _redraw_unselected_text(self): """ Redraw text where none has been selected by a user. """ self.text_surface = render_white_text_alpha_black_bg(font=self.font, text=self.text) if self.is_enabled: if isinstance(self.text_colour, ColourGradient): self.text_colour.apply_gradient_to_surface(self.text_surface) else: apply_colour_to_surface(self.text_colour, self.text_surface) else: if isinstance(self.disabled_text_colour, ColourGradient): self.disabled_text_colour.apply_gradient_to_surface( self.text_surface) else: apply_colour_to_surface(self.disabled_text_colour, self.text_surface)
def _draw_text_with_grad_or_col( self, text: str, col_or_grad: Union[ColourGradient, pygame.Color] ) -> pygame.surface.Surface: """ Draw text to a surface using either a colour or gradient. :param text: The text to render. :param col_or_grad: A colour or a colour gradient. :return: A surface with the text on. """ text_surface = render_white_text_alpha_black_bg(font=self.font, text=text) if isinstance(col_or_grad, ColourGradient): col_or_grad.apply_gradient_to_surface(text_surface) else: apply_colour_to_surface(col_or_grad, text_surface) return text_surface
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 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)
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 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 __init__(self, font_size: int, font_name: str, chunk: str, style: CharStyle, colour: Union[pygame.Color, ColourGradient], bg_colour: Union[pygame.Color, ColourGradient], is_link: bool, link_href: str, link_style: CharStyle, position: Tuple[int, int], font_dictionary: UIFontDictionary): self.style = style self.chunk = chunk self.font_size = font_size self.font_name = font_name self.is_link = is_link self.link_href = link_href self.link_style = link_style self.font = font_dictionary.find_font(font_size, font_name, self.style.bold, self.style.italic) if self.is_link: self.normal_colour = self.link_style['link_text'] self.hover_colour = self.link_style['link_hover'] self.selected_colour = self.link_style['link_selected'] self.link_normal_underline = self.link_style['link_normal_underline'] self.link_hover_underline = self.link_style['link_hover_underline'] else: self.normal_colour = colour self.hover_colour = None self.selected_colour = None self.link_normal_underline = False self.link_hover_underline = False self.colour = self.normal_colour self.bg_colour = bg_colour self.position = position self.is_hovered = False self.is_selected = False if self.style.underline or (self.is_hovered and self.link_hover_underline) or \ (self.link_normal_underline and not self.is_hovered): self.font.set_underline(True) if len(self.chunk) > 0: if not isinstance(self.colour, ColourGradient): if isinstance(self.bg_colour, ColourGradient) or self.bg_colour.a != 255: self.rendered_chunk = render_white_text_alpha_black_bg(self.font, self.chunk) apply_colour_to_surface(self.colour, self.rendered_chunk) else: self.rendered_chunk = self.font.render(self.chunk, True, self.colour, self.bg_colour).convert_alpha() else: self.rendered_chunk = render_white_text_alpha_black_bg(self.font, self.chunk) self.colour.apply_gradient_to_surface(self.rendered_chunk) else: self.rendered_chunk = pygame.surface.Surface((0, 0), flags=pygame.SRCALPHA, depth=32) metrics = self.font.metrics(self.chunk) self.ascent = self.font.get_ascent() self.width = self.font.size(self.chunk)[0] self.height = self.font.size(self.chunk)[1] self.advance = 0 for i in range(len(self.chunk)): if len(metrics[i]) == 5: self.advance += metrics[i][4] self.rect = pygame.Rect(self.position, (self.width, self.height)) self.metrics_changed_after_redraw = False self.unset_underline_style()