Source code for menus.esc
# ../menus/esc.py
"""Provides ESC Menu functionality."""
# =============================================================================
# >> IMPORTS
# =============================================================================
# Source.Python Imports
# Colors
from colors import WHITE
# Entities
from entities.helpers import edict_from_index
# Keyvalues
from keyvalues import KeyValues
# Menus
from menus.base import _BaseMenu
from menus.base import _PagedMenuBase
from menus.base import _BaseOption
from menus.base import _translate_text
from menus.queue import ESC_SELECTION_CMD
from menus.queue import _esc_queues
# Messages
from _messages import DialogType
from _messages import create_message
# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = ('ListESCMenu',
'ListESCOption',
'PagedESCMenu',
'PagedESCOption',
'SimpleESCMenu',
'SimpleESCOption',
)
# =============================================================================
# >> CONSTANTS
# =============================================================================
VALID_CHOICES = range(8)
# =============================================================================
# >> CLASSES
# =============================================================================
[docs]class SimpleESCMenu(_BaseMenu):
"""This class creates basic ESC menus."""
[docs] def __init__(
self, data=None, select_callback=None, build_callback=None, close_callback=None,
description=None, title=None, title_color=WHITE):
"""Initialize the object.
:param iterable|None data: See :meth:`menus.base._BaseMenu.__init__`.
:param callable|None select_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None build_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None close_callback: See
:meth:`menus.base._BaseMenu.__init__`
:param str|None description: A description that is displayed under the
title.
:param str|None title: A title that is displayed at the top of the
menu.
:param Color title_color: The color of the title.
"""
super().__init__(data, select_callback, build_callback, close_callback)
self.description = description
self.title = title
self.title_color = title_color
def _get_menu_data(self, player_index):
"""Return all menu data as a :class:`keyvalues.KeyValues` object.
:param int player_index: See
:meth:`menus.base._BaseMenu._get_menu_data`.
"""
data = KeyValues('menu')
data.set_string(
'msg', _translate_text(self.description or '', player_index))
if self.title is not None:
data.set_string(
'title', _translate_text(self.title, player_index))
data.set_color('color', self.title_color)
page = self._player_pages[player_index]
page.options = {}
# Loop through all options of the current page
for raw_data in self:
if isinstance(raw_data, SimpleESCOption):
page.options[raw_data.choice_index] = raw_data
button = data.find_key(str(raw_data.choice_index), True)
button.set_string('msg', raw_data._render(player_index))
button.set_string('command', '{0} {1}'.format(
ESC_SELECTION_CMD, raw_data.choice_index))
close = SimpleESCOption(0, 'Close')
button = data.find_key(str(close.choice_index), True)
button.set_string('msg', close._render(player_index))
button.set_string('command', '{0} {1}'.format(
ESC_SELECTION_CMD, close.choice_index))
# Return the menu data
return data
def _select(self, player_index, choice_index):
"""See :meth:`menus.base._BaseMenu._select`."""
if choice_index == 0:
return self._select_close(player_index)
option = self._player_pages[player_index].options[choice_index]
if not option.selectable:
return self
return super()._select(player_index, option)
def _send(self, player_index):
"""Build and send the menu to the given player via create_message().
:param int player_index: See :meth:`menus.base._BaseMenu._send`.
"""
queue = self.get_user_queue(player_index)
queue.priority -= 1
# Build the menu
data = self._build(player_index)
# Set priority and display time
data.set_int('level', queue.priority)
data.set_int('time', 10)
# Send the menu
create_message(
edict_from_index(player_index),
DialogType.MENU,
data
)
def _close(self, player_index):
"""See :meth:`menus.base._BaseMenu._close`."""
queue = self.get_user_queue(player_index)
queue.priority -= 1
# Unfortunately, this doesn't hide the menu :(
data = KeyValues('menu')
data.set_string('title', '')
data.set_int('level', queue.priority)
data.set_int('time', 10)
data.set_string('msg', '')
create_message(edict_from_index(player_index), DialogType.MENU, data)
@staticmethod
def _get_queue_holder():
"""Return the queue for ESC menus."""
return _esc_queues
[docs]class PagedESCMenu(SimpleESCMenu, _PagedMenuBase):
"""Create menus with an unlimited number of options.
Navigation options will be added automatically.
"""
[docs] def __init__(
self, data=None, select_callback=None, build_callback=None, close_callback=None,
description=None, title=None, title_color=WHITE, fill=True,
parent_menu=None):
"""Initialize the object.
:param iterable|None data: See :meth:`menus.base._BaseMenu.__init__`.
:param callable|None select_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None build_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None close_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param str|None description: See :meth:`SimpleESCMenu.__init__`.
:param str|None title: See :meth:`SimpleESCMenu.__init__`.
:param Color title_color: See :meth:`SimpleESCMenu.__init__`.
:param bool fill: If True the menu will always have the same size by
filling unused options.
:param _BaseMenu parent_menu: A menu that will be displayed when
hitting 'Back' on the first page.
"""
super().__init__(
data, select_callback, build_callback, close_callback,
description, title, title_color)
self.fill = fill
self.parent_menu = parent_menu
@staticmethod
def _get_max_item_count():
"""Return the maximum possible item count per page."""
return 5
def _format_header(self, player_index, page, data):
"""Prepare the header for the menu.
:param int player_index: A player index.
:param _PlayerPage page: The player's current page.
:param KeyValues data: The current menu data.
"""
# Create the page info string
info = '[{0}/{1}]'.format(page.index + 1, self.page_count)
if self.title is not None:
data.set_string('title', '{0} {1}'.format(
_translate_text(self.title, player_index), info))
else:
data.set_string('title', info)
data.set_color('color', self.title_color)
def _format_body(self, player_index, page, data):
"""Prepare the body for the menu.
:param int player_index: A player index.
:param _PlayerPage page: The player's current page.
:param KeyValues data: The current menu data.
"""
# Loop through all options of the current page
for index, option in enumerate(self._get_options(page.index), 1):
if isinstance(option, PagedESCOption):
page.options[index] = option
button = data.find_key(str(index), True)
button.set_string('msg', option._render(player_index, index))
button.set_string(
'command', '{0} {1}'.format(ESC_SELECTION_CMD, index))
# Fill the rest of the menu with empty options
if self.fill:
option_num = len(page.options)
for index in range(self._get_max_item_count() - option_num):
index += option_num + 1
button = data.find_key(str(index), True)
button.set_string('msg', '')
@staticmethod
def _format_footer(player_index, page, data):
"""Prepare the footer for the menu.
:param int player_index: A player index.
:param _PlayerPage page: The player's current page.
:param KeyValues data: The current menu data.
"""
# TODO: Add translations
# Add "Back" option
button = data.find_key('6', True)
button.set_string('msg', '6. Back')
button.set_string('command', '{0} 6'.format(ESC_SELECTION_CMD))
# Add "Next" option
button = data.find_key('7', True)
button.set_string('msg', '7. Next')
button.set_string('command', '{0} 7'.format(ESC_SELECTION_CMD))
# Add "Close" option
button = data.find_key('0', True)
button.set_string('msg', '0. Close')
button.set_string('command', '{0} 0'.format(ESC_SELECTION_CMD))
def _get_menu_data(self, player_index):
"""See :meth:`menus.base._BaseMenu._get_menu_data`."""
data = KeyValues('menu')
data.set_string(
'msg', _translate_text(self.description or '', player_index))
# Get the player's current page
page = self._player_pages[player_index]
page.options = {}
# Format the menu
self._format_header(player_index, page, data)
self._format_body(player_index, page, data)
self._format_footer(player_index, page, data)
# Return the menu data
return data
def _select(self, player_index, choice_index):
"""See :meth:`menus.base._BaseMenu._select`."""
if choice_index == 0:
del self._player_pages[player_index]
return self._select_close(player_index)
# Get the player's current page
page_index = self.get_player_page(player_index)
# Display previous page?
if choice_index == 6:
# Is the player on the first page, and do we have a parent menu?
if not page_index and self.parent_menu is not None:
return self.parent_menu
self.set_player_page(player_index, page_index - 1)
return self
# Display next page?
if choice_index == 7:
self.set_player_page(player_index, page_index + 1)
return self
return super()._select(player_index, choice_index)
[docs]class ListESCMenu(PagedESCMenu):
"""Creates a list-like ESC menu.
Navigation options are added automatically."""
[docs] def __init__(
self, data=None, select_callback=None, build_callback=None, close_callback=None,
description=None, title=None, title_color=WHITE, fill=True,
parent_menu=None, items_per_page=5):
"""Initialize the object.
:param iterable|None data: See :meth:`menus.base._BaseMenu.__init__`.
:param callable|None select_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None build_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param callable|None close_callback: See
:meth:`menus.base._BaseMenu.__init__`.
:param str|None description: See :meth:`SimpleESCMenu.__init__`.
:param str|None title: See :meth:`SimpleESCMenu.__init__`.
:param Color title_color: See :meth:`SimpleESCMenu.__init__`.
:param bool fill: See :meth:`PagedESCMenu.__init__`.
:param _BaseMenu parent_menu: See :meth:`PagedESCMenu.__init__`.
:param int items_per_page: Number of options that should be displayed
on a single page (5 is the maximum).
"""
super().__init__(data, select_callback, build_callback, close_callback, description,
title, title_color, fill, parent_menu)
self.items_per_page = items_per_page
def _get_max_item_count(self):
return self.items_per_page
[docs]class SimpleESCOption(_BaseOption):
"""Provides options for :class:`SimpleESCMenu` objects."""
[docs] def __init__(
self, choice_index, text, value=None,
highlight=True, selectable=True):
"""Initialize the option.
:param int choice_index: The number that is required to select the
option.
:param str text: See :meth:`menus.base._BaseOption.__init__`.
:param value: See :meth:`menus.base._BaseOption.__init__`.
:param bool hightlight: Does not work with ESC menus.
:param bool selectable: Does not work with ESC menus.
"""
super().__init__(text, value, highlight, selectable)
self.choice_index = choice_index
def _render(self, player_index, choice_index=None):
"""See :meth:`menus.base._MenuData._render`."""
return '{0}. {1}'.format(
self.choice_index, _translate_text(self.text, player_index))
[docs]class PagedESCOption(_BaseOption):
"""Display an enumerated option."""
def _render(self, player_index, choice_index=None):
"""See :meth:`menus.base._MenuData._render`."""
return '{0}. {1}'.format(
choice_index, _translate_text(self.text, player_index))
[docs]class ListESCOption(PagedESCOption):
"""Provides options for :class:`ListESCMenu` objects."""
[docs] def __init__(self, text, highlight=True, enumerated=True):
"""Initialize the option.
:param str text: See :meth:`menus.base._BaseOption.__init__`.
:param bool hightlight: See :meth:`SimpleESCMenu.__init__`.
:param bool enumerated: If True the number of the option will be added
in front of the text.
"""
super().__init__(text, None, highlight, False)
self.enumerated = enumerated
def _render(self, player_index, choice_index=None):
"""See :meth:`menus.base._MenuData._render`."""
if self.enumerated:
return super()._render(player_index, choice_index)
return _translate_text(self.text, player_index)