Source code for pygame_menu.widgets.widget.button
"""
pygame-menu
https://github.com/ppizarror/pygame-menu
BUTTON
Button class, manage elements and adds entries to Menu.
"""
__all__ = ['Button']
import pygame
import pygame_menu
import pygame_menu.controls as ctrl
from pygame_menu.locals import FINGERUP
from pygame_menu.utils import is_callable, assert_color, get_finger_pos
from pygame_menu.widgets.core.widget import Widget
from pygame_menu._types import Any, CallbackType, Callable, Union, List, Tuple, \
Optional, ColorType, ColorInputType, EventVectorType
# noinspection PyMissingOrEmptyDocstring
[docs]class Button(Widget):
"""
Button widget.
The arguments and unknown keyword arguments are passed to the ``onreturn``
function:
.. code-block:: python
onreturn(*args, **kwargs)
.. note::
Button accepts all transformations.
:param title: Button title
:param button_id: Button ID
:param onreturn: Callback when pressing the button
:param args: Optional arguments for callbacks
:param kwargs: Optional keyword arguments
"""
_last_underline: List[Union[str, Optional[Tuple[ColorType, int, int]]]] # deco id, (color, offset, width)
to_menu: bool
def __init__(
self,
title: Any,
button_id: str = '',
onreturn: CallbackType = None,
*args,
**kwargs
) -> None:
super(Button, self).__init__(
args=args,
kwargs=kwargs,
onreturn=onreturn,
title=title,
widget_id=button_id
)
self._accept_events = True
self._last_underline = ['', None]
self.to_menu = False # True if the button opens a new Menu
def _apply_font(self) -> None:
pass
[docs] def set_selection_callback(
self,
callback: Optional[Callable[[bool, 'Widget', 'pygame_menu.Menu'], Any]]
) -> None:
"""
Update the button selection callback, once button is selected, the callback
function is executed as follows:
.. code-block:: python
callback(selected, widget, menu)
:param callback: Callback when selecting the widget, executed in :py:meth:`pygame_menu.widgets.core.widget.Widget.set_selected`
:return: None
"""
if callback is not None:
assert is_callable(callback), \
'callback must be callable (function-type) or None'
self._onselect = callback
[docs] def update_callback(self, callback: Callable, *args) -> None:
"""
Update function triggered by the button; ``callback`` cannot point to a Menu, that
behaviour is only valid using :py:meth:`pygame_menu.menu.Menu.add.button` method.
.. note::
If button points to a submenu, and the callback is changed to a
function, the submenu will be removed from the parent Menu. Thus
preserving the structure.
:param callback: Function
:param args: Arguments used by the function once triggered
:return: None
"""
assert is_callable(callback), \
'only callable (function-type) are allowed'
# If return is a Menu object, remove it from submenus list
if self._menu is not None and self._onreturn is not None and self.to_menu:
assert len(self._args) == 1
submenu = self._args[0] # Menu
assert self._menu.in_submenu(submenu), \
'pointed menu is not in submenu list of parent container'
# noinspection PyProtectedMember
assert self._menu._remove_submenu(submenu, self), \
'submenu could not be removed'
self.to_menu = False
self._args = args or []
self._onreturn = callback
[docs] def add_underline(
self,
color: ColorInputType,
offset: int,
width: int,
force_render: bool = False
) -> 'Button':
"""
Adds a underline to text. This is added if widget is rendered.
:param color: Underline color
:param offset: Underline offset
:param width: Underline width
:param force_render: If ``True`` force widget render after addition
:return: Self reference
"""
color = assert_color(color)
assert isinstance(offset, int)
assert isinstance(width, int) and width > 0
self._last_underline[1] = (color, offset, width)
if force_render:
self._force_render()
return self
[docs] def remove_underline(self) -> 'Button':
"""
Remove underline of the button.
:return: Self reference
"""
if self._last_underline[0] != '':
self._decorator.remove(self._last_underline[0])
self._last_underline[0] = ''
return self
def _draw(self, surface: 'pygame.Surface') -> None:
surface.blit(self._surface, self._rect.topleft)
def _render(self) -> Optional[bool]:
if not self._render_hash_changed(
self._selected, self._title, self._visible, self.readonly,
self._last_underline[1]):
return True
# Render surface
self._surface = self._render_string(self._title, self.get_font_color_status())
self._apply_transforms()
self._rect.width, self._rect.height = self._surface.get_size()
# Add underline if enabled
self.remove_underline()
if self._last_underline[1] is not None:
w = self._surface.get_width()
h = self._surface.get_height()
color, offset, width = self._last_underline[1]
if w > 0 and h > 0:
self._last_underline[0] = self._decorator.add_line(
pos1=(-w / 2, h / 2 + offset),
pos2=(w / 2, h / 2 + offset),
color=color,
width=width
)
self.force_menu_surface_update()
[docs] def update(self, events: EventVectorType) -> bool:
self.apply_update_callbacks(events)
if self.readonly or not self.is_visible():
return False
rect = self.get_rect(to_real_position=True)
for event in events:
# Check mouse over
self._check_mouseover(event, rect)
# User applies with key
if event.type == pygame.KEYDOWN and self._keyboard_enabled and \
event.key == ctrl.KEY_APPLY or \
event.type == pygame.JOYBUTTONDOWN and self._joystick_enabled and \
event.button == ctrl.JOY_BUTTON_SELECT:
if self.to_menu:
self._sound.play_open_menu()
else:
self._sound.play_key_add()
self.apply()
return True
# User clicks the button; don't consider the mouse wheel (button 4 & 5)
elif event.type == pygame.MOUSEBUTTONUP and self._mouse_enabled and \
event.button in (1, 2, 3) or \
event.type == FINGERUP and self._touchscreen_enabled and \
self._menu is not None:
if event.type == pygame.MOUSEBUTTONUP:
self._sound.play_click_mouse()
else:
self._sound.play_click_touch()
event_pos = get_finger_pos(self._menu, event)
if rect.collidepoint(*event_pos):
self.apply()
return True
return False