Source code for tuxemon.ui.text

from __future__ import annotations

from typing import List, Literal, Optional, Tuple, Union

import pygame
from pygame.rect import Rect

from tuxemon.graphics import ColorLike
from tuxemon.sprite import Sprite
from tuxemon.ui import draw

min_font_size = 7


[docs]class TextArea(Sprite): """Area of the screen that can draw text.""" animated = True def __init__( self, font: pygame.font.Font, font_color: ColorLike, bg: ColorLike = (192, 192, 192), ) -> None: super().__init__() self.rect = Rect(0, 0, 0, 0) self.drawing_text = False self.font = font self.font_color = font_color self.font_bg = bg self._rendered_text = None self._text_rect = None self._text = "" def __iter__(self) -> TextArea: return self def __len__(self) -> int: return len(self._text) @property def text(self) -> str: return self._text @text.setter def text(self, value: str) -> None: if value != self._text: self._text = value if self.animated: self._start_text_animation() else: self.image = draw.shadow_text( self.font, self.font_color, self.font_bg, self._text, ) def __next__(self) -> None: if self.animated: try: dest, scrap = next(self._iter) self.image.blit(scrap, dest) except StopIteration: self.drawing_text = False raise else: raise StopIteration next = __next__ def _start_text_animation(self) -> None: self.drawing_text = True self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA) self._iter = draw.iter_render_text( self._text, self.font, self.font_color, self.font_bg, self.image.get_rect(), )
[docs]def draw_text( surface: pygame.surface.Surface, text: str, rect: Union[Rect, Tuple[int, int, int, int]], *, justify: Literal["left", "center", "right"] = "left", align: Literal["top", "middle", "bottom"] = "top", font: pygame.font.Font, font_size: Optional[int] = None, font_color: Optional[ColorLike] = None, ) -> None: """ Draws text to a surface. If the text exceeds the rect size, it will autowrap. To place text on a new line, put TWO newline characters (\\n) in your text. Parameters: text: The text that you want to draw to the current menu item. rect: Area where the text will be placed. justify: Left, center, or right justify the text. align: Align the text to the top, middle, or bottom of the menu. font: Font to use to draw the text. font_size: Size of the font in pixels BEFORE scaling is done. *Default: 4* font_color: Tuple of RGB values of the font _color to use. .. image:: images/menu/justify_center.png """ left, top, width, height = rect _left: float = left _top: float = top if not font_color: font_color = (0, 0, 0) if not text: return # Create a text surface so we can determine how many pixels # wide each character is text_surface = font.render(text, True, font_color) # Calculate the number of pixels per letter based on the size # of the text and the number of characters in the text pixels_per_letter = text_surface.get_width() / len(text) # Create a list of the lines of text as well as a list of the # individual words so we can check each line's length in pixels lines: List[str] = [] wordlist: List[str] = [] # Loop through each word in the text and add it to the word list for word in text.split(): # If there is a linebreak in this word, then split it up into a list separated by \n if "\\n" in word: w = word.split("\\n") # Loop through the list and every time we encounter a blank string, then that is # a new line. So we append the current line and reset the word list for a new line for item in w: if item == "": # This is a new line! lines.append(" ".join(wordlist)) wordlist = [] # If we encounter an actual word, then just append it to the word list else: wordlist.append(item) # If there's no line break, continue normally to word wrap else: # Append the word to the current line wordlist.append(word) # Here, we convert the list into a string separated by spaces and multiply # the number of characters in the string by the number of pixels per letter # that we calculated earlier. This will let us know how large the text will # be in pixels. if len(" ".join(wordlist)) * pixels_per_letter > width: # If the size exceeds the width of the menu, then append the line to the # list of lines, but stripping off the last word we added (because this # was the word that made us exceed the menubox's size). lines.append(" ".join(wordlist[:-1])) # Reset the wordlist for the next line and add the word we stripped off wordlist = [] wordlist.append(word) # If the last line is not blank, then append it to the list if " ".join(wordlist) != "": lines.append(" ".join(wordlist)) # If the justification was set, handle the position of the text automatically if justify == "center": if lines: _left = (left + (width / 2)) - ( (len(lines[0]) * pixels_per_letter) / 2 ) else: _left = 0 elif justify == "right": raise NotImplementedError("Needs to be implemented") # If text alignment was set, handle the position of the text automatically if align == "middle": _top = (top + (height / 2)) - ( (text_surface.get_height() * len(lines)) / 2 ) elif align == "bottom": raise NotImplementedError("Needs to be implemented") # Set a spacing variable that we will add to space each line. spacing = 0 for item in lines: line = font.render(item, True, font_color) surface.blit(line, (_left, _top + spacing)) spacing += line.get_height() # + self.line_spacing