Source code for nqp.ui_elements.generic.ui_tooltip
from __future__ import annotations
from typing import TYPE_CHECKING
import pygame
from nqp.core.constants import FontType, GAP_SIZE, WindowType
from nqp.ui_elements.generic.ui_frame import UIFrame
from nqp.ui_elements.generic.ui_window import UIWindow
if TYPE_CHECKING:
from typing import Dict, List, Tuple
from nqp.core.definitions import UIContainerLike
from nqp.core.game import Game
__all__ = ["UITooltip"]
[docs]class UITooltip(UIWindow):
"""
A UIWindow subclass for showing specific information about a hovered item.
"""
[docs] def __init__(
self,
game: Game,
pos: pygame.Vector2,
tooltip_key: str | None = None,
tooltip_content: Tuple[str, str, str] | None = None,
):
"""
Needs either tooltip_key or tooltip_content. tooltip_key takes precedent over
tooltip_content, if both are provided only the key is used.
"""
super().__init__(game, WindowType.BASIC, pos, pygame.Vector2(0, 0), [], True)
self.secondary_tooltips: None | UIWindow = None
self.max_width: int = 100
# get the content from either param
if tooltip_key is not None:
content = self._get_content(tooltip_key)
if content is not None:
title, text, image_name = content
self._build_self(title, text, image_name)
elif tooltip_content is not None:
title, text, image_name = tooltip_content
self._build_self(title, text, image_name)
[docs] def draw(self, surface: pygame.Surface):
super().draw(surface)
if self.secondary_tooltips:
self.secondary_tooltips.draw(surface)
def _get_content(self, tooltip_key: str) -> Tuple[str, str, str] | None:
"""
Get the content from the tooltip data, using the key.
Returns None if key not found.
"""
try:
title = self._game.data.tooltips[tooltip_key]["title"]
text = self._game.data.tooltips[tooltip_key]["text"]
image_name = self._game.data.tooltips[tooltip_key]["image"]
return title, text, image_name
except KeyError:
return
def _build_self(self, title: str, text: str, image_name: str):
"""
Build primary and secondary tooltips.
Also recalcs size of self and rebuilds the window surface to align to the new size.
"""
text, keys = self._parse_text(text)
pos = pygame.Vector2(self.pos.x + GAP_SIZE, self.pos.y + GAP_SIZE)
frames = self._build_frames(title, text, image_name, pos)
self._elements = self._elements + frames
# need to recalc size and rebuild window to match
self._recalculate_size()
self._window_surface = self._build_window_surface()
# build secondary tooltips
pos = pygame.Vector2(self.pos.x + self.width + GAP_SIZE, self.pos.y + GAP_SIZE)
secondary_frames = []
for key in keys:
title, text, image_name = self._get_content(key)
text, keys = self._parse_text(text)
frames = self._build_frames(title, text, image_name, pos)
secondary_frames = secondary_frames + frames
# increment pos
last_frame = frames[-1]
pos = pygame.Vector2(pos.x, last_frame.pos.y + last_frame.height + 1) # +1 avoid overlap
# add secondary frames to a window
if secondary_frames:
# recalc size
height = 0
width = 0
for frame in secondary_frames:
height += frame.height + 1
if frame.width > width:
width = frame.width
# add gap for whitespace
width += GAP_SIZE
height += GAP_SIZE * 2
pos = pygame.Vector2(self.pos.x + self.width + 1, self.pos.y) # +1 avoid overlap
# create window, not tooltip, as we've already done all the work and we dont want recursive tooltips
self.secondary_tooltips = UIWindow(
self._game, self._window_type, pos, pygame.Vector2(width, height), secondary_frames, True
)
@staticmethod
def _parse_text(text: str) -> Tuple[str, List[str]]:
"""
Return text without tags and a list of any keys for secondary tooltips.
"""
# check for secondary tags
start_indices = [i for i, char in enumerate(text) if char == "<"]
end_indices = [i for i, char in enumerate(text) if char == ">"]
secondary_tooltip_keys = []
# confirm results are as expected
if len(start_indices) != 0 and len(start_indices) == len(end_indices):
# loop indices, remove tags and keep strings in tags for getting secondary tooltips
for i, index in enumerate(start_indices):
end = end_indices[i]
secondary_tooltip_keys.append(text[index + 1 : end])
# remove tags from text
text = text.replace("<", "")
text = text.replace(">", "")
return text, secondary_tooltip_keys
def _recalculate_size(self):
"""
Recalculate the size of the tooltip based on the the elements.
"""
height = 0
width = 0
for frame in self._elements:
height += frame.height + 1 # account for overlap offset
# get biggest width
if frame.width > width:
width = frame.width
# add gap for whitespace
width += GAP_SIZE
height += GAP_SIZE * 2
self.size = pygame.Vector2(width, height)
def _build_frames(self, title: str, text: str, image_name: str, pos: pygame.Vector2) -> List[UIFrame]:
"""
Build the various frames that make up the tooltip
"""
frames = []
# title frame
font = self._game.visual.create_font(FontType.POSITIVE, title)
if image_name:
image = self._game.visual.get_image(image_name)
frame = UIFrame(self._game, pos, font, image=image)
else:
frame = UIFrame(self._game, pos, font)
frames.append(frame)
height = frame.height
# content frame
font = self._game.visual.create_font(FontType.DEFAULT, text)
pos = pygame.Vector2(pos.x, pos.y + frame.height + 1) # +1 avoid overlap
frame = UIFrame(self._game, pos, font, max_width=self.max_width)
frames.append(frame)
height += frame.height
return frames