Source code for scripts.nqp.ui_elements.character_selector

from __future__ import annotations

import logging
from typing import Callable, Optional, TYPE_CHECKING, Union

import pygame
import pygame_gui
from pygame import Rect
from pygame_gui import UIManager
from pygame_gui.core import ObjectID
from pygame_gui.elements import UIButton, UIDropDownMenu, UIImage, UITextBox

from scripts.engine.core.utility import build_sprites_from_paths
from scripts.engine.internal import library
from scripts.engine.internal.constant import GAP_SIZE, RenderLayer, TILE_SIZE, TraitGroup
from scripts.engine.internal.definition import ActorData
from scripts.engine.internal.event import event_hub, StartGameEvent
from scripts.engine.widgets.panel import Panel

if TYPE_CHECKING:
    from typing import Dict, List

__all__ = ["CharacterSelector"]


[docs]class CharacterSelector(Panel): """ A menu widget. Used to hold a collection of text, images, buttons etc. Expects to have buttons and provides functionality to handle the clicks """
[docs] def __init__(self, rect: Rect, manager: UIManager): self.button_events: Dict[str, Union[pygame.event.Event, Callable]] = { "confirm": self._post_confirm_event, } # containers for created widgets self.buttons: List[UIButton] = [] self.drop_downs: List[UIDropDownMenu] = [] self.text_boxes: List[UITextBox] = [] self.ui_image: Optional[UIImage] = None # complete base class init super().__init__(rect, RenderLayer.BOTTOM, manager, object_id=ObjectID("#character_selector", "@menu_screen")) self._init_buttons() self._init_drop_downs() self._refresh_info() # confirm init complete logging.debug(f"CharacterSelector initialised.")
[docs] def update(self, time_delta: float): """ Update based on current state and data. Run every frame. """ super().update(time_delta)
[docs] def process_event(self, event): super().process_event(event) # only progress for user events if event.type != pygame.USEREVENT: return # handle button presses if event.user_type == pygame_gui.UI_BUTTON_PRESSED: # Find out which button we are clicking button = event.ui_element # post the new event if button in self.buttons: # get the id ids = event.ui_object_id.split(".") button_id = ids[-1] # get last element new_event = self.button_events[button_id] # process the func from the button new_event() logging.debug(f"CharacterSelector button '{button_id}' pressed.") # handle new selection in drop downs if event.user_type == pygame_gui.UI_DROP_DOWN_MENU_CHANGED: self._refresh_info()
def _init_buttons(self): """ Init the buttons for the menu """ manager = self.ui_manager # set button dimensions max_width = self.rect.width max_height = self.rect.height height = int(max_height / 12) width = int(max_width / 6) x = -width # anchor top right y = 0 button = UIButton( relative_rect=Rect((x, y), (width, height)), anchors={"left": "right", "right": "right", "top": "top", "bottom": "bottom"}, manager=manager, container=self, text="Embark", tool_tip_text="Confirm your selection and embark on your adventure.", object_id="confirm", ) self.buttons.append(button) def _init_drop_downs(self): """ Initialise the drop downs for the menu """ people = [] savvy = [] homeland = [] manager = self.ui_manager max_width = self.rect.width max_height = self.rect.height num_drop_downs = 3 gap = GAP_SIZE width = int((max_width / num_drop_downs) - (gap * num_drop_downs + 2)) height = int(max_height / 12) start_x = 0 start_y = int(max_height / 2) # anchored to the bottom left max_expansion = int(start_y - height) # get traits for key, trait in library.TRAITS.items(): if trait.group == TraitGroup.PEOPLE: people.append(key) elif trait.group == TraitGroup.SAVVY: savvy.append(key) elif trait.group == TraitGroup.HOMELAND: homeland.append(key) # bundle trait lists with group name all_traits = [(homeland, TraitGroup.SAVVY), (people, TraitGroup.PEOPLE), (savvy, TraitGroup.SAVVY)] # create drop downs count = 0 for traits, trait_group in all_traits: x = start_x + (count * (width + gap)) rect = Rect((x, start_y), (width, height)) drop_down = UIDropDownMenu( options_list=traits, starting_option=traits[0], relative_rect=rect, manager=manager, container=self, object_id=trait_group, expansion_height_limit=max_expansion, ) count += 1 self.drop_downs.append(drop_down) def _refresh_info(self): """ Update the image and text in line with drop down selections """ # image dimensions image_width = TILE_SIZE * 4 image_height = TILE_SIZE * 4 image_x = int((self.rect.width / 2) - (image_width / 2)) image_y = int(self.rect.height / 10) image_rect = Rect((image_x, image_y), (image_width, image_height)) # clear ui image if it exists if self.ui_image: self.ui_image.kill() self.ui_image = None # clear text box if it exists if self.text_boxes: for text_box in self.text_boxes: text_box.kill() self.text_boxes = [] # get info from traits sprite_paths = [] info = {} info_rects = [] text_size = 4 col = "#ffffff" for drop_down in self.drop_downs: trait = library.TRAITS[drop_down.selected_option] sprite_paths.append(trait.sprite_paths) info[trait.name] = [ f"<font face=cozette color={col} size={text_size}>" # f"{trait.name} <br>", f"{trait.description} <br>", f"<br>", f"Clout: {trait.clout} <br>", f"Vigour: {trait.vigour} <br>", f"Skullduggery: {trait.skullduggery} <br>", f"Bustle: {trait.bustle} <br>", f"Exactitude: {trait.exactitude} <br>", f"Permanent Afflictions: {trait.permanent_afflictions} <br>", f"</font", ] # info dimensions drop_down_rect = drop_down.rect info_width = drop_down_rect.width info_height = int((self.rect.height - drop_down_rect.y) - drop_down_rect.height) info_x = drop_down_rect.x info_y = drop_down_rect.y + drop_down_rect.height info_rects.append(Rect((info_x, info_y), (info_width, info_height))) # sort and build into sprites sprite_paths.sort(key=lambda path: path.render_order, reverse=True) sprites = build_sprites_from_paths(sprite_paths, (image_width, image_height)) # create UI image ui_image = UIImage( relative_rect=image_rect, image_surface=sprites.idle, manager=self.ui_manager, container=self, ) self.ui_image = ui_image # create the text boxes count = 0 for name, details in info.items(): text_box = UITextBox( html_text=" ".join(details), relative_rect=info_rects[count], manager=self.ui_manager, object_id="name", container=self, ) count += 1 self.text_boxes.append(text_box) def _post_confirm_event(self): """ Post the confirm event to be picked up elsewhere. Includes the selected data. """ # get trait names traits = [] for drop_down in self.drop_downs: traits.append(drop_down.selected_option) # get player data player_data = ActorData( key="player", possible_names=["player"], description="Player desc", position_offsets=[(0, 0)], trait_names=traits, height="middling", # type: ignore # need to pass as str to be converted during post-init ) # post game event event_hub.post(StartGameEvent(player_data))