from __future__ import annotations
import logging
import os
from typing import TYPE_CHECKING
import pygame
from pygame_gui import UIManager
from scripts.engine.core import utility
from scripts.engine.internal import debug, library
from scripts.engine.internal.constant import ASSET_PATH, DATA_PATH, UIElement
from scripts.engine.widgets.screen_message import ScreenMessage
if TYPE_CHECKING:
from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union
from scripts.engine.widgets.panel import Panel
from scripts.engine.widgets.window import Window
__all__ = ["ui"]
[docs]class UI:
"""
Manage the UI, such as windows, resource bars etc
"""
[docs] def __init__(self):
self.debug_font = None
# first action needs to be to init pygame.
pygame.init()
pygame.font.init()
# get config info
base_window_data = library.VIDEO_CONFIG.base_window
desired_window_data = library.VIDEO_CONFIG.desired_window
# pull out to reduce use of accessor
base_width = base_window_data.width
base_height = base_window_data.height
desired_width = desired_window_data.width
desired_height = desired_window_data.height
## set the display
# base values
self.base_width = base_width
self.base_height = base_height
self._main_surface: pygame.Surface = pygame.Surface((base_width, base_height), pygame.SRCALPHA)
# values to scale to
self.desired_width = desired_width
self.desired_height = desired_height
self._window: pygame.display = pygame.display.set_mode((desired_width, desired_height))
# now that the display is configured init the pygame_gui
self._gui = UIManager((desired_width, desired_height), DATA_PATH / "ui/themes.json")
# elements info
self._elements: Dict[UIElement, Union[Panel, Window]] = {} # dict of all init'd ui_manager elements
# process config
self._load_display_config()
self._load_fonts()
logging.info(f"UIManager initialised.")
################ CORE METHODS ########################
[docs] def update(self, time_delta: float):
"""
Update all ui_manager elements
"""
self._gui.update(time_delta)
[docs] def process_ui_events(self, event):
"""
Pass event to the gui manager.
"""
self._gui.process_events(event)
[docs] def draw(self):
"""
Draw the UI.
"""
# draw everything
self._draw_debug()
self._gui.draw_ui(self._window)
pygame.display.flip() # make sure to do this as the last drawing element in a frame
def _draw_debug(self):
"""
Draw debug information, based on visible values in debug.
"""
values = debug.get_visible_values()
y = 10
debug_font = self.debug_font
for value in values:
surface = debug_font.render(value, False, (255, 255, 255))
self._main_surface.blit(surface, (0, y))
y += 10
##################### GET ############################
[docs] def get_element(self, element_type: UIElement) -> Union[Panel, Window]:
"""
Get UI element.
"""
if element_type in self._elements:
return self._elements[element_type]
element_name = utility.value_to_member(element_type, UIElement)
raise KeyError(f"Tried to get {element_name} ui element but key not found.")
[docs] def get_gui_manager(self) -> UIManager:
"""
Return the pygame_gui UI Manager
"""
return self._gui
##################### INIT, LOAD AND CREATE ############################
@staticmethod
def _load_display_config():
"""
Initialise display settings.
"""
pygame.display.set_caption("Not Quite Paradise")
pygame.display.set_icon(utility.get_image("ui/nqp_icon.png"))
def _load_fonts(self):
self._gui.add_font_paths("barlow", str(ASSET_PATH / "fonts/Barlow-Light.otf"))
self._gui.add_font_paths("cozette", str(ASSET_PATH / "fonts/cozette_bitmap.ttf"))
self.debug_font = pygame.font.Font(str(ASSET_PATH / "fonts/Barlow-Light.otf"), 6)
fonts = [
{"name": "barlow", "point_size": 12, "style": "regular"},
{"name": "cozette", "point_size": 12, "style": "regular"},
]
self._gui.preload_fonts(fonts)
[docs] def register_element(self, element_type: UIElement, element: Union[Panel, Window]):
"""
Register the specified UI element. Can be returned with get_element at a later date. If it already exists
current instance will be overwritten.
"""
# if it already exists, log that is being overwritten
# N.B. do not use get_element to check as it will create a circular reference
if element_type in self._elements:
element_name = utility.value_to_member(element_type, UIElement)
logging.warning(f"Created new {element_name} ui element, overwriting previous instance.")
self._elements[element_type] = element
[docs] def create_screen_message(self, message: str, colour: str = "#531B75", size: int = 4):
"""
Create a message on the screen.
"""
text = f"<font face=cozette color={colour} size={size}>{message}</font>"
rect = pygame.Rect((self.base_width / 4, self.base_height / 4), (self.base_width / 2, -1))
ScreenMessage(rect, text, self.get_gui_manager())
######################## KILL ###############################################
[docs] def kill_all_elements(self):
"""
Close and kill the game's UI elements. Helper function to run kill_element on all elements.
"""
elements = self._elements.copy()
for element_type in elements.keys():
self.kill_element(element_type)
[docs] def kill_element(self, element_type: UIElement):
"""
Remove any reference to the element.
"""
element = self.get_element(element_type)
del self._elements[element_type]
element.kill()
################################ QUERIES #################################################################
[docs] def element_is_visible(self, element_type: UIElement) -> bool:
"""
Check if an element is visible.
"""
if self.has_element(element_type):
element = self.get_element(element_type)
return element.visible
else:
return False
[docs] def element_is_active(self, element_type: UIElement) -> bool:
"""
Check if an element has been created and is visible
"""
if element_type in self._elements:
return self.element_is_visible(element_type)
else:
return False
[docs] def has_element(self, element_type: UIElement) -> bool:
"""
Check if an element exists
"""
if element_type in self._elements:
return True
else:
return False
################################ UNIVERSAL ACTIONS #############################################
[docs] def set_element_visibility(self, element_type: UIElement, visible: bool) -> bool:
"""
Set whether the element is visible or not. Returns true if successful, false if element not found.
"""
if not self.has_element(element_type):
return False
element = self.get_element(element_type)
element.visible = visible
element_name = utility.value_to_member(element_type, UIElement)
if visible:
element.show()
logging.debug(f"Showed {element_name} ui element.")
else:
element.hide()
logging.debug(f"Hid {element_name} ui element.")
return True
if "GENERATING_SPHINX_DOCS" not in os.environ: # when building in CI these fail
ui = UI()
else:
ui = "" # type: ignore