# -*- coding: latin-1 -*-
# pylint: disable=too-many-lines
"""
simpleguics2pygame module: simpleguics2pygame/frame.
Class Frame.
Piece of SimpleGUICS2Pygame.
https://bitbucket.org/OPiMedia/simpleguics2pygame
:license: GPLv3 --- Copyright (C) 2015-2016, 2020 Olivier Pirson
:author: Olivier Pirson --- http://www.opimedia.be/
:version: November 29, 2020
"""
from __future__ import division
from __future__ import print_function
# print('IMPORT', __name__)
import os.path
import random
import sys
try:
from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
except ImportError:
pass
import pygame
__all__ = ('Frame',
'create_frame')
from SimpleGUICS2Pygame.simpleguics2pygame._arguments import _CONFIG # pylint: disable=no-name-in-module # noqa
from SimpleGUICS2Pygame.simpleguics2pygame import _colors, _fonts, _joypads, _media # pylint: disable=wrong-import-position,ungrouped-imports,unused-import # noqa
from SimpleGUICS2Pygame.simpleguics2pygame._colors import _SIMPLEGUICOLOR_TO_PYGAMECOLOR, _simpleguicolor_to_pygamecolor # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame._fonts import _SIMPLEGUIFONTFACE_TO_PYGAMEFONTNAME, _simpleguifontface_to_pygamefont # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame.canvas import Canvas # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame.control import Control, TextAreaControl # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame.image import Image # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame.keys import KEY_MAP, _SIMPLEGUIKEY_TO_STATUSKEY, _pygamekey_to_simpleguikey # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
from SimpleGUICS2Pygame.simpleguics2pygame.timer import _STOP_TIMERS, Timer, create_timer # pylint: disable=wrong-import-position,no-name-in-module,ungrouped-imports # noqa
#
# Class
#######
[docs]class Frame: # pylint: disable=too-many-instance-attributes
"""Frame similar to SimpleGUI `Frame` of CodeSkulptor."""
_background_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['white']
"""Default background color of frame."""
_canvas_border_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['black']
"""Border color of canvas."""
_controlpanel_background_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['white'] # pylint: disable=invalid-name # noqa
"""Background color of control panel."""
_cursor_auto_hide = False
"""
When move cursor,
if `True`
then hide cursor when on canvas,
else show cursor.
"""
_display_fps_average = _CONFIG['--display-fps']
"""
If `True`
then display FPS average on the canvas.
"""
_fps = _CONFIG['--fps']
"""Frames per second drawed (frequency of draw and check events)"""
_frame_padding = _CONFIG['--frame-padding']
"""The padding in pixels around the canvas"""
_frame_instance = None
"""The only instance of Frame."""
_keep_timers = (True if _CONFIG['--keep-timers']
else (False if _STOP_TIMERS
else None))
"""
If `None`
then ask (when stop frame) if it should be stop timers when program ending.
(This is the default behavior.)
If `True`
then timers keep running when program ending.
If `False`
then stop all timers when program ending.
"""
_hide_controlpanel = _CONFIG['--no-controlpanel']
"""
If `True`
then hide control panel (and status box).
"""
_hide_status = _CONFIG['--no-status']
"""
If `True`
then hide status box.
"""
_print_stats_cache = _CONFIG['--print-stats-cache']
"""
If `True`
then print some statistics of caches after frame stopped.
"""
_pygame_mode_flags = ((pygame.FULLSCREEN | pygame.HWSURFACE # pylint: disable=no-member # noqa
if _CONFIG['--fullscreen']
else 0) |
(pygame.NOFRAME # pylint: disable=no-member
if _CONFIG['--no-border']
else 0))
"""
Default options of graphic mode.
See https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode
"""
_pygame_mode_depth = 0
"""
Default number of bits used to represent color.
See https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode
"""
_save_canvas_requests = [] # type: List[str]
"""List of filenames in which to save canvas image."""
_statuskey_background_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['white'] # pylint: disable=invalid-name # noqa
"""`pygame.Color` of background in status key box."""
_statuskey_height = 20
"""Height of the status key box."""
_statuskey_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['black']
"""`pygame.Color` of status key box (text and rectangle)."""
_statuskey_pygame_font = pygame.font.Font(None, _statuskey_height)
"""`pygame.font.Font` of status key box."""
_statusmouse_background_pygame_color = _SIMPLEGUICOLOR_TO_PYGAMECOLOR['white'] # pylint: disable=invalid-name # noqa
"""`pygame.Color` of background in status mouse box."""
_statusmouse_height = _statuskey_height
"""Height of the status mouse box."""
_statusmouse_pygame_color = _statuskey_pygame_color
"""`pygame.Color` of status mouse box (text and rectangle)."""
_statusmouse_pygame_font = pygame.font.Font(None, _statusmouse_height)
"""`pygame.font.Font` of status mouse box."""
[docs] @classmethod
def _pygamecolors_cached_clear(cls): # type: () -> None
"""
Empty the cache of Pygame colors used.
Each color used is cached to accelerate drawing.
If you use many many different colors maybe use this function
to free memory.
**(Not available in SimpleGUI of CodeSkulptor.)**
Side effect: Empty `_colors._PYGAMECOLORS_CACHED`.
""" # noqa
_colors._PYGAMECOLORS_CACHED = dict() # pylint: disable=protected-access # noqa
[docs] @classmethod
def _pygamefonts_cached_clear(cls): # type: () -> None
"""
Empty the cache of Pygame fonts used.
Each font used with each size is cached to accelerate drawing.
If you use many many different sizes maybe use this function
to free memory.
**(Not available in SimpleGUI of CodeSkulptor.)**
Side effect: Empty `_fonts.__PYGAMEFONTS_CACHED`.
"""
_fonts.__PYGAMEFONTS_CACHED = dict() # pylint: disable=protected-access # noqa
[docs] @classmethod
def _set_cursor_visible(cls, visible=True): # type: (bool) -> None
"""
If visible is `True`
then show cursor,
else hide cursor.
Independently of `_cursor_auto_hide` value.
:param visible: bool
"""
pygame.mouse.set_visible(visible)
[docs] def __init__(self, # pylint: disable=too-many-statements
title,
canvas_width, canvas_height,
control_width=200):
# type: (str, Union[int, float], Union[int, float], Union[int, float]) -> None # noqa
"""
Set the frame.
**Don't use directly**, use create_frame().
:param title: str
:param canvas_width: (int or float) >= 0
:param canvas_height: (int or float) >= 0
:param control_width: (int or float) >= 0
"""
assert Frame._frame_instance is None, \
"You can't instantiate two Frame!"
assert isinstance(title, str), type(title)
assert isinstance(canvas_width, (int, float)), type(canvas_width)
assert canvas_width >= 0, canvas_width
assert isinstance(canvas_height, (int, float)), type(canvas_height)
assert canvas_height >= 0, canvas_height
assert isinstance(control_width, (int, float)), type(control_width)
assert control_width >= 0, control_width
Frame._frame_instance = self
self._control_width = (0 if Frame._hide_controlpanel
else int(round(control_width)))
self._border_size = (0 if Frame._hide_controlpanel
else 25)
self._canvas_border_size = Frame._frame_padding
self._canvas_x_offset = (self._control_width + self._border_size * 2 +
self._canvas_border_size)
self._canvas_y_offset = self._border_size + self._canvas_border_size
self._controls = [] # type: List[Union[Control, TextAreaControl]]
self._control_next_y = 10
self._control_selected = None
self._fps_average = 0
self.__joypad_down_handler = None # type: Optional[Callable[[int, int], Any]] # noqa
self.__joypad_up_handler = None # type: Optional[Callable[[int, int], Any]] # noqa
self.__joypad_axe_handler = None # type: Optional[Callable[[int, int, float], Any]] # noqa
self.__joypad_hat_handler = None # type: Optional[Callable[[int, int, Tuple[int, int]], Any]] # noqa
self._key_down_handler = None # type: Optional[Callable[[int], Any]]
self._key_up_handler = None # type: Optional[Callable[[int], Any]]
self._mouse_click_handler = None # type: Optional[Callable[[Tuple[int, int]], Any]] # noqa
self._mouse_drag_handler = None # type: Optional[Callable[[Tuple[int, int]], Any]] # noqa
self._running = False
canvas_width = int(round(canvas_width))
canvas_height = int(round(canvas_height))
self._statusmouse_x_offset = 0
self._statusmouse_y_offset = (self._canvas_y_offset + canvas_height -
Frame._statusmouse_height)
self._statuskey_x_offset = self._statusmouse_x_offset
self._statuskey_y_offset = (self._statusmouse_y_offset -
5 - Frame._statuskey_height)
# Create the window
icon_path = __file__.split(os.path.sep)[:-1]
try:
icon_path.extend(('_img', 'SimpleGUICS2Pygame_64x64_t.png'))
pygame.display.set_icon(
pygame.image.load(os.path.sep.join(icon_path)))
except: # pylint: disable=bare-except # noqa
pass
self._pygame_surface = pygame.display.set_mode(
((self._canvas_x_offset + canvas_width +
self._canvas_border_size + self._border_size),
(self._canvas_y_offset + canvas_height +
self._canvas_border_size + self._border_size)),
Frame._pygame_mode_flags,
Frame._pygame_mode_depth)
pygame.display.set_caption(title)
self._pygame_surface.fill(Frame._background_pygame_color)
for i in range(1, self._canvas_border_size + 1):
pygame.draw.rect(
self._pygame_surface, Frame._canvas_border_pygame_color,
(self._canvas_x_offset - i,
self._canvas_y_offset - i,
canvas_width + 2 * i,
canvas_height + 2 * i),
1)
# Create the canvas
self._canvas = Canvas(self, canvas_width, canvas_height)
# Create the status boxes: key and mouse
self._statuskey_pygame_surface = pygame.surface.Surface( # pylint: disable=protected-access,too-many-function-args # noqa
(self._control_width, Frame._statuskey_height))
self._statusmouse_pygame_surface = pygame.surface.Surface( # pylint: disable=protected-access,too-many-function-args # noqa
(self._control_width, Frame._statusmouse_height))
# will be drawn by self._draw_controlpanel()
# Create the control panel
self._controlpanel_pygame_surface = pygame.surface.Surface( # pylint: disable=protected-access,too-many-function-args # noqa
(self._control_width, canvas_height))
self._draw_controlpanel()
# Display all
pygame.display.update()
[docs] def __repr__(self): # type: () -> str
"""
Return '<Frame object>'.
:return: str
"""
return '<Frame object>'
def __deal_event_key(self, event): # type: (pygame.event.Event) -> bool
"""
Private function that dispatch key `event`.
:param event: Pygame event
:return: True if some event match, else False
"""
if event.type == pygame.KEYDOWN: # key pressed # pylint: disable=no-member,no-else-return # noqa
if ((self._control_selected is not None) and
isinstance(self._control_selected,
TextAreaControl)):
self._control_selected._key(event) # pylint: disable=protected-access # noqa
elif self._key_down_handler is not None:
key = _pygamekey_to_simpleguikey(event.key)
self._draw_statuskey(key, True)
self._key_down_handler(key)
return True
elif event.type == pygame.KEYUP: # key released # pylint: disable=no-member # noqa
if ((self._control_selected is not None) and
isinstance(self._control_selected,
TextAreaControl)):
pass
elif self._key_up_handler is not None:
key = _pygamekey_to_simpleguikey(event.key)
self._draw_statuskey(key, False)
self._key_up_handler(key)
return True
else:
return False
def __deal_event_joypad(self, event): # type: (pygame.event.Event) -> bool
"""
Private function that dispatch joypad `event`.
**(Not available in SimpleGUI of CodeSkulptor.)**
:param event: Pygame event
:return: True if some event match, else False
"""
if event.type == pygame.JOYHATMOTION: # hat moved # pylint: disable=no-member,no-else-return # noqa
if self.__joypad_hat_handler is not None:
self.__joypad_hat_handler(event.joy, event.hat, event.value)
return True
elif event.type == pygame.JOYAXISMOTION: # axe moved # pylint: disable=no-member # noqa
if self.__joypad_axe_handler is not None:
self.__joypad_axe_handler(event.joy, event.axis, event.value)
return True
elif event.type == pygame.JOYBUTTONDOWN: # button pressed # pylint: disable=no-member # noqa
if self.__joypad_down_handler is not None:
self.__joypad_down_handler(event.joy, event.button)
return True
elif event.type == pygame.JOYBUTTONUP: # button release # pylint: disable=no-member # noqa
if self.__joypad_up_handler is not None:
self.__joypad_up_handler(event.joy, event.button)
return True
else:
return False
def __deal_event_mouse(self, event): # pylint: disable=too-many-branches
# type: (pygame.event.Event) -> bool
"""
Private function that dispatch mouse `event`.
:param event: Pygame event
:return: True if some event match, else False
"""
mouse_drag_out_of_canvas = None
if event.type == pygame.MOUSEMOTION: # mouse moved # pylint: disable=no-member,no-else-return # noqa
x = event.pos[0] - self._canvas_x_offset
y = event.pos[1] - self._canvas_y_offset
if self._cursor_auto_hide:
pygame.mouse.set_visible(not((0 <= x < self._canvas._width) and (0 <= y < self._canvas._height))) # pylint: disable=protected-access # noqa
if self._mouse_drag_handler is not None:
if pygame.mouse.get_pressed()[0]: # type: ignore
# Left click
if (not 0 <= x < self._canvas._width) or (not 0 <= y < self._canvas._height): # pylint: disable=protected-access # noqa
# Out of canvas
mouse_drag_out_of_canvas = True
if not mouse_drag_out_of_canvas:
# In canvas
# and not out of canvas
# since last mouse left button pressed
self._draw_statusmouse((x, y), True)
self._mouse_drag_handler((x, y))
return True
elif event.type == pygame.MOUSEBUTTONDOWN: # mouse b. pressed # pylint: disable=no-member # noqa
if event.button == 1: # left click
x = event.pos[0] - self._canvas_x_offset
y = event.pos[1] - self._canvas_y_offset
if (0 <= x < self._canvas._width) and (0 <= y < self._canvas._height): # pylint: disable=protected-access # noqa
# In canvas
mouse_drag_out_of_canvas = False
elif x < 0:
# In control panel
control = self._pos_in_control(event.pos[0] -
self._border_size,
event.pos[1] -
self._canvas_y_offset)
if control is not None:
control._mouse_left_button(True) # pylint: disable=protected-access # noqa
elif self._control_selected is not None:
self._control_selected = None
self._draw_controlpanel()
elif self._control_selected is not None:
self._control_selected = None
self._draw_controlpanel()
return True
elif event.type == pygame.MOUSEBUTTONUP: # mouse b. released # pylint: disable=no-member # noqa
if event.button == 1: # left click
x = event.pos[0] - self._canvas_x_offset
y = event.pos[1] - self._canvas_y_offset
if (0 <= x < self._canvas._width) and (0 <= y < self._canvas._height): # pylint: disable=protected-access # noqa
# In canvas
if self._mouse_click_handler is not None:
self._draw_statusmouse((x, y), False)
self._mouse_click_handler((x, y))
elif x < 0:
# In control panel
control = self._pos_in_control(event.pos[0] -
self._border_size, y)
if control is not None:
control._mouse_left_button(False) # pylint: disable=protected-access # noqa
return True
else:
return False
[docs] def _cursor_in_canvas(self): # type: () -> bool
""":return: `True` if the cursor is on canvas, `False` else."""
x, y = pygame.mouse.get_pos()
x -= self._canvas_x_offset
y -= self._canvas_y_offset
return (0 <= x < self._canvas._width) and (0 <= y < self._canvas._height) # pylint: disable=protected-access # noqa
[docs] def _draw_controlpanel(self): # type: () -> None
"""
Draw the control panel
and two status boxes.
**(Not available in SimpleGUI of CodeSkulptor.)**
"""
self._controlpanel_pygame_surface.fill(
Frame._controlpanel_background_pygame_color)
for control in self._controls:
control._draw() # pylint: disable=protected-access
if Frame._hide_controlpanel:
return
self._pygame_surface.blit(self._controlpanel_pygame_surface,
(self._border_size,
self._canvas_y_offset))
self._draw_statuskey()
self._draw_statusmouse()
pygame.display.update((self._border_size,
self._canvas_y_offset,
self._control_width,
self._canvas._height)) # pylint: disable=protected-access # noqa
[docs] def _draw_statuskey(self, key=0, pressed=None):
# type: (int, Optional[bool]) -> None
"""
Draw the status box of key.
**(Not available in SimpleGUI of CodeSkulptor.)**
:param key: int
:param pressed: None or bool
"""
assert isinstance(key, int), type(key)
assert (pressed is None) or isinstance(pressed, bool), type(pressed)
if Frame._hide_status or Frame._hide_controlpanel:
return
self._statuskey_pygame_surface.fill(
Frame._statuskey_background_pygame_color)
pygame.draw.rect(self._statuskey_pygame_surface,
Frame._statuskey_pygame_color,
(0, 0, self._control_width, Frame._statuskey_height),
1)
if pressed is not None:
statuskey = _SIMPLEGUIKEY_TO_STATUSKEY.get(key, key)
text = 'Key: {} {}'.format(('Down' if pressed
else 'Up'),
(statuskey if isinstance(statuskey, str)
else '<{}>'.format(statuskey)))
else:
text = 'Key:'
pygame_surface_text = Frame._statuskey_pygame_font.render(
text, True, Frame._statuskey_pygame_color)
self._statuskey_pygame_surface.blit(
pygame_surface_text,
(5,
(Frame._statuskey_height - pygame_surface_text.get_height()) / 2))
self._pygame_surface.blit(self._statuskey_pygame_surface,
((self._border_size +
self._statuskey_x_offset),
self._statuskey_y_offset))
pygame.display.update((self._border_size + self._statuskey_x_offset,
self._statuskey_y_offset,
self._control_width,
Frame._statuskey_height))
[docs] def _draw_statusmouse(self, position=(0, 0), pressed=None):
# type: (Sequence[Union[int, float]], bool) -> None
"""
Draw the status box of mouse.
**(Not available in SimpleGUI of CodeSkulptor.)**
:param position: (int or float, int or float)
or [int or float, int or float]
:param pressed: bool
"""
assert isinstance(position, (tuple, list)), type(position)
assert len(position) == 2, len(position)
assert isinstance(position[0], (int, float)), type(position[0])
assert isinstance(position[1], (int, float)), type(position[1])
assert (pressed is None) or isinstance(pressed, bool), type(pressed)
if Frame._hide_status or Frame._hide_controlpanel:
return
self._statusmouse_pygame_surface.fill(
Frame._statusmouse_background_pygame_color)
pygame.draw.rect(self._statusmouse_pygame_surface,
Frame._statusmouse_pygame_color,
(0, 0,
self._control_width, Frame._statusmouse_height), 1)
text = ('Mouse: {} {}, {}'.format(('Move' if pressed
else 'Click'),
position[0], position[1])
if pressed is not None
else 'Mouse:')
pygame_surface_text = Frame._statusmouse_pygame_font.render(
text, True, Frame._statusmouse_pygame_color)
self._statusmouse_pygame_surface.blit(
pygame_surface_text,
(5,
(Frame._statusmouse_height - pygame_surface_text.get_height()) /
2))
self._pygame_surface.blit(self._statusmouse_pygame_surface,
((self._border_size +
self._statusmouse_x_offset),
self._statusmouse_y_offset))
pygame.display.update((self._border_size + self._statusmouse_x_offset,
self._statusmouse_y_offset,
self._control_width,
Frame._statusmouse_height))
[docs] def _get_fps_average(self): # type: () -> float
"""
Return the framerate average (in frame per second) computed by Pygame.
**(Not available in SimpleGUI of CodeSkulptor.)**
:return: float
"""
return float(self._fps_average)
[docs] def _pos_in_control(self, x, y):
# type: (Union[int, float], Union[int, float]) -> Optional[Union[Control, TextAreaControl]] # noqa
"""
If position (`x`, `y`)
is on the zone of one `Control` or `TextAreaControl`
then return it
else return `None`.
**(Not available in SimpleGUI of CodeSkulptor.)**
:param x: int or float
:param y: int or float
:return: None or Control or TextAreaControl
"""
assert isinstance(x, (int, float)), type(x)
assert isinstance(y, (int, float)), type(y)
if (self._controls and
(self._controls[0]._y1 <= y <= self._controls[-1]._y2)): # pylint: disable=protected-access # noqa
for control in self._controls:
if control._pos_in(x, y): # pylint: disable=protected-access # noqa
return control
return None
[docs] def _save_canvas_request(self, filename): # type: (str) -> None
"""
Request to save the canvas image in a file.
(The images are saved on each cycle fixed by `Frame._fps`.)
**(Not available in SimpleGUI of CodeSkulptor.)**
:param filename: str
"""
assert isinstance(filename, str), type(filename)
filename = os.path.abspath(os.path.expanduser(filename))
self._save_canvas_requests.append(filename)
[docs] def _save_canvas_and_stop(self, filename, after=1000):
# type: (str, Union[int, float]) -> None
"""
Wait after ms (first wait until the frame is started),
then save the canvas in a file
and stop the program.
**(Not available in SimpleGUI of CodeSkulptor.)**
:param filename: str
:param after: int or float >= 0
"""
assert isinstance(filename, str), type(filename)
assert isinstance(after, (int, float)), type(after)
assert after >= 0, after
filename = os.path.abspath(os.path.expanduser(filename))
def save_canvas_and_stop():
"""Handler function will be executed."""
if self._running:
self._save_canvas_request(filename)
Timer._stop_all() # pylint: disable=protected-access # noqa
self.stop()
if after == 0:
save_canvas_and_stop()
else:
timer = create_timer(after, save_canvas_and_stop)
timer.start()
[docs] def _set_canvas_background_image(self, image):
# type: (Optional[Image]) -> None
"""
Set an image to replace the background color of the canvas.
:param image: None or Image
"""
assert (image is None) or isinstance(image, Image), type(image)
self._canvas._bg_pygame_surface_image = ( # pylint: disable=protected-access # noqa
None if image is None
else image._pygame_surface) # pylint: disable=protected-access
[docs] def _set_joypadaxe_handler(self, joypad_handler):
# type: (Callable[[int, int, float], Any]) -> None
"""
Set the function handler
that will be executed
(with the joypad index, the axe index and the value)
when axis of joypad move.
(The events are checked on each cycle fixed by `Frame._fps`.)
**(Not available in SimpleGUI of CodeSkulptor.)**
:param joypad_handler: function (int >= 0, int >=0, -1 <= float <= 1) -> *
""" # noqa
assert callable(joypad_handler), type(joypad_handler)
self.__joypad_axe_handler = joypad_handler
[docs] def _set_joypaddown_handler(self, joypad_handler):
# type: (Callable[[int, int], Any]) -> None
"""
Set the function handler
that will be executed (with the joypad index and the button index)
when a button of joypad is **pressed**.
(The events are checked on each cycle fixed by `Frame._fps`.)
**(Not available in SimpleGUI of CodeSkulptor.)**
:param joypad_handler: function (int >= 0, int >= 0) -> *
"""
assert callable(joypad_handler), type(joypad_handler)
self.__joypad_down_handler = joypad_handler
[docs] def _set_joypadhat_handler(self, joypad_handler):
# type: (Callable[[int, int, Tuple[int, int]], Any]) -> None
"""
Set the function handler
that will be executed
(with the joypad index, the hat index
and the values (a, b) where a and b == -1, 0 or 1)
when hat of joypad move.
(The events are checked on each cycle fixed by `Frame._fps`.)
**(Not available in SimpleGUI of CodeSkulptor.)**
:param joypad_handler: function (int >= 0, int >= 0, (int, int)) -> *
"""
assert callable(joypad_handler), type(joypad_handler)
self.__joypad_hat_handler = joypad_handler
[docs] def _set_joypadup_handler(self, joypad_handler):
# type: (Callable[[int, int], Any]) -> None
"""
Set the function handler
that will be executed (with the joypad index and the button index)
when a button of joypad is **released**.
(The events are checked on each cycle fixed by `Frame._fps`.)
**(Not available in SimpleGUI of CodeSkulptor.)**
:param joypad_handler: function (int >= 0, int >= 0) -> *
"""
assert callable(joypad_handler), type(joypad_handler)
self.__joypad_up_handler = joypad_handler
[docs] def add_label(self, text, width=None):
# type: (str, Optional[int]) -> Control
"""
Add a label in the control panel.
If `width` is not `None`
then `text` is possibly cutted.
But, in CodeSkulptor, the accurate appearance is browser dependent.
And in SimpleGUICS2Pygame, the accurate appearance is font dependent.
:param text: str
:param width: None or int
:return: Control
"""
assert isinstance(text, str), type(text)
assert (width is None) or isinstance(width, int), type(width)
control = Control(self, text, width=width)
self._controls.append(control)
self._draw_controlpanel()
return control
[docs] def download_canvas_image(self, filename='canvas.png'):
# type: (str) -> None
r"""
Save the content of the canvas in a local file.
In SimpleGUICS2Pygame
supported formats are supported formats by Pygame to save:
TGA, PNG, JPEG or BMP
(see https://www.pygame.org/docs/ref/image.html#pygame.image.save ).
If `filename` extension is not recognized
then TGA format is used.
If `filename` == ''
then a random filename is used,
beginning by 'canvas\_' and with '.png' extension.
In CodeSkulptor the format is always PNG.
(Available in SimpleGUI of CodeSkulptor3
but *not in CodeSkulptor 2*
and *not in CodeSkulptor documentation*!)
:param filename: str
"""
assert isinstance(filename, str), type(filename)
if filename == '':
filename = 'canvas_{}.png'.format(''.join(random.choice('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') for _ in range(8))) # noqa
self._canvas._save(filename) # pylint: disable=protected-access
[docs] def get_canvas_image(self): # type: () -> None
"""
NOT YET IMPLEMENTED! (Does nothing.)
(Available in SimpleGUI of CodeSkulptor
but *not in CodeSkulptor documentation*!)
"""
[docs] def get_canvas_textwidth(self, # pylint: disable=no-self-use
text,
font_size,
font_face='serif'):
# type: (str, Union[int, float], str) -> Union[int, float]
"""
Return the width needed to draw `text` by `Frame.draw_text()`.
:param text: str
:param font_size: (int or float) >= 0
:param font_face: str == 'monospace', 'sans-serif', 'serif'
:return: int or float >= 0
"""
assert isinstance(text, str), type(text)
assert isinstance(font_size, (int, float)), type(font_size)
assert font_size >= 0, font_size
assert isinstance(font_face, str), type(font_face)
assert font_face in _SIMPLEGUIFONTFACE_TO_PYGAMEFONTNAME, font_face
font_size = int(round(font_size))
return (_simpleguifontface_to_pygamefont(font_face,
font_size).size(text)[0]
if font_size > 0
else 0)
[docs] def set_canvas_background(self, color): # type: (str) -> None
"""
Set the background color of the canvas.
:param color: str
"""
assert isinstance(color, str), type(color)
self._canvas._background_pygame_color = _simpleguicolor_to_pygamecolor(color) # pylint: disable=protected-access # noqa
[docs] def set_draw_handler(self, draw_handler):
# type: (Callable[[Canvas], Any]) -> None
"""
Set the function handler
that will be executed each cycle fixed by `Frame._fps`.
:param draw_handler: function (Canvas) -> *
"""
assert callable(draw_handler), type(draw_handler)
self._canvas._draw_handler = draw_handler # pylint: disable=protected-access # noqa
[docs] def set_keydown_handler(self, key_handler):
# type: (Callable[[int], Any]) -> None
"""
Set the function handler
that will be executed (with the key code) when a key is released.
(The events are checked on each cycle fixed by `Frame._fps`.)
:param key_handler: function (int >= 0) -> *
"""
assert callable(key_handler), type(key_handler)
self._key_down_handler = key_handler
[docs] def set_keyup_handler(self, key_handler):
# type: (Callable[[int], Any]) -> None
"""
Set the function handler
that will be executed (with the key code) when a key is pressed.
(The events are checked on each cycle fixed by `Frame._fps`.)
:param key_handler: function (int >= 0) -> *
"""
assert callable(key_handler), type(key_handler)
self._key_up_handler = key_handler
[docs] def set_mouseclick_handler(self, mouse_handler):
# type: (Callable[[Tuple[int, int]], Any]) -> None
"""
Set the function handler
that will be executed (with the position of the mouse)
when the left button of mouse is **released**.
(The events are checked on each cycle fixed by `Frame._fps`.)
:param mouse_handler: function ((int >= 0, int >= 0)) -> *
"""
assert callable(mouse_handler), type(mouse_handler)
self._mouse_click_handler = mouse_handler
[docs] def set_mousedrag_handler(self, mouse_handler):
# type: (Callable[[Tuple[int, int]], Any]) -> None
"""
Set the function handler
that will be executed (with the position of the mouse)
**for each** new mouse position
when the left button of mouse is pressed.
(The events are checked on each cycle fixed by `Frame._fps`.)
:param mouse_handler: function ((int >= 0, int >= 0)) -> *
"""
assert callable(mouse_handler), type(mouse_handler)
self._mouse_drag_handler = mouse_handler
[docs] def start(self): # type: () -> None
"""
Start the frame and these handler events.
.. warning::
With SimpleGUICS2Pygame,
``Frame.start()`` is blocking
until ``Frame.stop()`` execution or closing window.
So timers must be started *before*,
and states must be initialized *before*.
(Or maybe after by a handler function.)
(In SimpleGUI of CodeSkulptor this function is *not* blocking.)
"""
self._running = True
clock = pygame.time.Clock()
# Core of the drawing canvas and dealing events
while self._running: # pylint: disable=too-many-nested-blocks
# Draw canvas
self._canvas._draw() # pylint: disable=protected-access # noqa
# Save canvas images
while Frame._save_canvas_requests:
self._canvas._save(Frame._save_canvas_requests.pop(0)) # pylint: disable=protected-access # noqa
# Check events
for event in pygame.event.get():
if (not self.__deal_event_mouse(event) and
not self.__deal_event_joypad(event) and
not self.__deal_event_key(event) and
event.type == pygame.QUIT): # pylint: disable=no-member # noqa
self.stop()
# Wait (if necessary) next cycle
self._fps_average = int(round(clock.get_fps()))
clock.tick(Frame._fps)
# clock.tick_busy_loop(Frame._fps)
self.stop()
while Frame._save_canvas_requests:
self._canvas._save(Frame._save_canvas_requests.pop(0)) # pylint: disable=protected-access # noqa
Frame._frame_instance = None
pygame.display.quit()
if Frame._print_stats_cache:
_print_stats_cache()
Frame._pygamecolors_cached_clear()
Frame._pygamefonts_cached_clear()
[docs] def stop(self): # type: () -> None
"""
Stop frame activities.
If (Frame._keep_timers is None) and there is still running timers
then ask in the canvas if it should be stop timers when program ending.
(Maybe available in SimpleGUI of CodeSkulptor
but *not in CodeSkulptor documentation*!)
"""
if (Frame._keep_timers is not None) or not Timer._running_some(): # pylint: disable=protected-access # noqa
# Don't ask (eventually because no running timers)
if not Frame._keep_timers:
Timer._stop_all() # pylint: disable=protected-access # noqa
self._running = False
else:
# There are running timers: ask if they should be stop
def check_key(key):
"""
If key is 'Y'
then stop timers and stop the frame.
If key is 'N'
then stop the frame.
:param key: int >= 0
"""
if key == KEY_MAP['Y']:
Timer._stop_all() # pylint: disable=protected-access # noqa
self._running = False
elif key == KEY_MAP['N']:
self._running = False
def draw_ask(canvas):
"""
Draw request about running timers.
:param canvas: simpleguics2pygame.Canvas
"""
if not Timer._running_some(): # pylint: disable=protected-access # noqa
self._running = False
nb_timers_running = Timer._running_nb() # pylint: disable=protected-access # noqa
size = 20
canvas.draw_text('Stop {} running timer{}'
.format(nb_timers_running,
('s' if nb_timers_running >= 2
else '')),
(10, 10 + size * 3 / 4), size, 'Black')
canvas.draw_text('when program ending?',
(10, 10 + size * 7 / 4), size, 'Black')
canvas.draw_text('(Yes/No)',
(10, 10 + size * 11 / 4), size, 'Black')
self.__joypad_down_handler = None
self.__joypad_up_handler = None
self.__joypad_axe_handler = None
self.__joypad_hat_handler = None
self._key_down_handler = None
self._key_up_handler = None
self._mouse_click_handler = None
self._mouse_drag_handler = None
Frame._hide_status = True
self._controls = []
self._draw_controlpanel()
self.set_draw_handler(draw_ask)
self.set_canvas_background('White')
self.set_keyup_handler(check_key)
#
# "Private" functions
#####################
def _print_stats_cache(): # type: () -> None
"""
Print to stderr some statistics of cached colors, fonts and medias.
**(Not available in SimpleGUI of CodeSkulptor.)**
"""
print("""# cached colors: {}
# cached fonts: {}
# cached medias: {}""".format(len(_colors._PYGAMECOLORS_CACHED), # pylint: disable=protected-access # noqa
len(_fonts.__PYGAMEFONTS_CACHED), # pylint: disable=protected-access # noqa
len(_media.__PYGAMEMEDIAS_CACHED)), # pylint: disable=protected-access # noqa
file=sys.stderr)
sys.stderr.flush()
#
# SimpleGUI function
####################
[docs]def create_frame(title,
canvas_width, canvas_height,
control_width=200):
# type: (str, Union[int, float], Union[int, float], Union[int, float]) -> Frame # noqa
"""
Create and return an interactive window. ::
| +-------+
| | title |
| +---------+--------------+
| | control | |
| | panel | canvas |
| | | |
| +---------+--------------+
| `title`: title of the window.
| `canvas_width`, canvas_height: dimensions of the canvas.
| `control_width`: width of the control panel.
(The frame is inactive until the execution of `Frame.start()`.)
**Don't run twice!**
:param title: str
:param canvas_width: (int or float) >= 0
:param canvas_height: (int or float) >= 0
:param control_width: (int or float) >= 0
:return: Frame
"""
assert isinstance(title, str), type(title)
assert isinstance(canvas_width, (int, float)), type(canvas_width)
assert canvas_width >= 0, canvas_width
assert isinstance(canvas_height, (int, float)), type(canvas_height)
assert canvas_height >= 0, canvas_height
assert isinstance(control_width, (int, float)), type(control_width)
assert control_width >= 0, control_width
return Frame(title, canvas_width, canvas_height, control_width)