Source code for SimpleGUICS2Pygame.simplegui_lib_loader

# -*- coding: latin-1 -*-

"""
simplegui_lib_loader module.

A class to help load images and sounds
in SimpleGUI of CodeSkulptor.

Piece of SimpleGUICS2Pygame.
https://bitbucket.org/OPiMedia/simpleguics2pygame

:license: GPLv3 --- Copyright (C) 2013-2015, 2020 Olivier Pirson
:author: Olivier Pirson --- http://www.opimedia.be/
:version: May 19, 2020
"""

# print('IMPORT', __name__)


try:
    from typing import Any, Callable, Dict, Optional, Union
except ImportError:
    pass

try:
    import simplegui  # pytype: disable=import-error  # pylint: disable=unused-import  # noqa
except ImportError:
    import SimpleGUICS2Pygame.simpleguics2pygame as simplegui  # type: ignore  # pylint: disable=unused-import  # noqa


# Class
########
[docs]class Loader: # pylint: disable=too-many-instance-attributes """ Help to load images and sounds from Internet and wait finished. With SimpleGUICS2Pygame, `SimpleGUICS2Pygame.load_image()` and `SimpleGUICS2Pygame.load_sound()` wait automatically until loading is completed. But in CodeSkulptor, the browser load images and sounds asynchronously. (With SimpleGUI it is **impossible to verify that the sounds are loaded**. So `Loader` begin load sounds, and next begin load images. It wait each image is loaded, and considers that all downloads are completed.) """ _interval = 100 """ Interval in ms betweed two check. """ __SIMPLEGUICS2PYGAME = False """ `True` if SimpleGUICS2Pygame are used, else `False`. """
[docs] def __init__(self, frame, progression_bar_width, after_function, max_waiting=5000): # type: (simplegui.Frame, Union[int, float], Callable[[], Any], Union[int, float]) -> None # noqa """ Set an empty loader. :param frame: simplegui.Frame :param progression_bar_width: (int or float) >= 0 :param after_function: function () -> * :param max_waiting: (int or float) >= 0 """ assert isinstance(progression_bar_width, (int, float)), \ type(progression_bar_width) assert progression_bar_width >= 0, progression_bar_width # assert callable(after_function), type(after_function) self._frame = frame self._progression_bar_width = progression_bar_width self._after_function = after_function self._max_waiting = max_waiting self._images = dict() # type: Dict[str, simplegui.Image] self._sounds = dict() # type: Dict[str, simplegui.Sound] self.__max_waiting_remain_started = False self.__max_waiting_remain = None # type: Optional[Union[int, float]] self.__timer = None # type: Optional[simplegui.Timer] try: # Try import to check if running in SimpleGUICS2Pygame environment from SimpleGUICS2Pygame.simpleguics2pygame import load_image # pylint: disable=import-outside-toplevel # noqa Loader.__SIMPLEGUICS2PYGAME = bool(load_image) except ImportError: pass
[docs] def _draw_loading(self, canvas): # type: (simplegui.Canvas) -> None """ Draw waiting message on the canvas when images and sounds loading. :param canvas: simplegui.Canvas """ nb = self.get_nb_images() + self.get_nb_sounds() size = 30 if (self._progression_bar_width > 0) and (nb > 0): percent = (self.get_nb_images_loaded() + self.get_nb_sounds_loaded()) * 100.0 / nb y = 30 + size * 3.0 / 4 canvas.draw_line((0, y), (self._progression_bar_width, y), 20, 'White') if percent > 0: canvas.draw_line((0, y), ((self._progression_bar_width * percent / 100.0), y), 20, 'Green') canvas.draw_text('Loading... %d%%' % int(percent), (10, 10 + size * 3.0 / 4), size, 'White') if self.__max_waiting_remain_started: assert isinstance(self.__max_waiting_remain, (int, float)) nb = int(round(self.__max_waiting_remain / 1000.0)) canvas.draw_text('Abort after %d second%s...' % (nb, ('s' if nb > 1 else '')), (10, 50 + size * 2 * 3.0 / 4), size, 'White')
[docs] def add_image(self, url, name=None): # type: (str, Optional[str]) -> None """ Add an image from `url` and give it a name. **Execute `Loader.load()` before use images.** If `name` == `None` then "filename" of url is used. Example: If `url` == `'http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png'` and `name` == `None` then `'asteroid_blue.png'` is used. :param url: str :param name: None or str """ # noqa assert isinstance(url, str), type(url) assert (name is None) or isinstance(name, str), type(name) self._images[(url.split('/')[-1] if name is None else name)] = url
[docs] def add_sound(self, url, name=None): # type: (str, Optional[str]) -> None """ Add a sound from `url` and give it a `name`. **Execute `Loader.load()` before use sounds.** If `name` == `None` then "filename" of `url` is used. Example: If `url` == `'http://commondatastorage.googleapis.com/codeskulptor-assets/Epoq-Lepidoptera.ogg'` and `name` == `None` then `'Epoq-Lepidoptera.ogg'` is used. :param url: str :param name: None or str """ # noqa assert isinstance(url, str), type(url) assert (name is None) or isinstance(name, str), type(name) self._sounds[(url.split('/')[-1] if name is None else name)] = url
[docs] def cache_clear(self): # type: () -> None """ * In standard Python with SimpleGUICS2Pygame: Empty the cache of Pygame surfaces used by each image of this Loader. See `Image._pygamesurfaces_cached_clear`_ . * In SimpleGUI of CodeSkulptor: do nothing. .. _`Image._pygamesurfaces_cached_clear`: simpleguics2pygame/image.html#SimpleGUICS2Pygame.simpleguics2pygame.image.Image._pygamesurfaces_cached_clear """ # noqa if Loader.__SIMPLEGUICS2PYGAME: for _, image in sorted(self._images.items()): image._pygamesurfaces_cached_clear() # type: ignore # pylint: disable=protected-access # noqa
[docs] def get_image(self, name): # type: (str) -> Optional[simplegui.Image] """ If an image named `name` exist then return it, else return `None` :param name: str :raise: Exception if Loader.load() was not executed since the addition of this image. :return: None or simplegui.Image """ assert isinstance(name, str), type(name) image = self._images.get(name) if isinstance(image, str): raise Exception( "load() not executed since the addition of the image '%s'!" % name) return image
[docs] def get_nb_images(self): # type: () -> int """ Return the number of images (loaded or not). :return: int >= 0 """ return len(self._images)
[docs] def get_nb_images_loaded(self): # type: () -> int """ Return the number of loaded images. It is the number of begin loading by `Loader.load()` **and** fully completed. :return: int >= 0 """ return len([None for name in self._images if ((not isinstance(self._images[name], str)) and (self._images[name].get_width() > 0))])
[docs] def get_nb_sounds(self): # type: () -> int """ Return the number of sounds (loaded or not). :return: int >= 0 """ return len(self._sounds)
[docs] def get_nb_sounds_loaded(self): # type: () -> int """ Return the number of loaded sounds. It is the number of begin loading by `Loader.load()`, **but not necessarily completed**. Because with SimpleGUI of CodeSkulptor it is **impossible to verify that the sounds are loaded**. :return: int >= 0 """ return len([None for name in self._sounds if not isinstance(self._sounds[name], str)])
[docs] def get_sound(self, name): # type: (str) -> Optional[simplegui.Sound] """ If a sound named `name` exist then return it, else return `None` :param name: str :raise: Exception if load() was not executed since the addition of this sound. :return: None or simplegui.Sound """ assert isinstance(name, str), type(name) sound = self._sounds.get(name) if isinstance(sound, str): raise Exception( "load() not executed since the addition of the sound '%s'!" % name) return sound
[docs] def load(self): # type: () -> None """ **Start loading** of all images and sounds added since last `Loader.load()` execution. * In standard Python with SimpleGUICS2Pygame: draw a progression bar on canvas and wait until the loading is finished. * In SimpleGUI of CodeSkulptor: *don't* wait. """ # noqa if Loader.__SIMPLEGUICS2PYGAME: from SimpleGUICS2Pygame.simpleguics2pygame import load_image, load_sound # pylint: disable=import-outside-toplevel # noqa else: from simplegui import load_image, load_sound # type: ignore # pytype: disable=import-error # pylint: disable=import-outside-toplevel,import-error # noqa if Loader.__SIMPLEGUICS2PYGAME: handler_saved = self._frame._canvas._draw_handler # pylint: disable=protected-access # noqa self._frame._canvas._draw_handler = self._draw_loading # pylint: disable=protected-access # noqa for name in self._sounds: if Loader.__SIMPLEGUICS2PYGAME: self._frame._canvas._draw() # pylint: disable=protected-access if isinstance(self._sounds[name], str): self._sounds[name] = load_sound(self._sounds[name]) for name in self._images: if Loader.__SIMPLEGUICS2PYGAME: self._frame._canvas._draw() # pylint: disable=protected-access if isinstance(self._images[name], str): self._images[name] = load_image(self._images[name]) if Loader.__SIMPLEGUICS2PYGAME: self._frame._canvas._draw() # pylint: disable=protected-access self._frame._canvas._draw_handler = handler_saved # pylint: disable=protected-access # noqa
[docs] def pause_sounds(self): # type: () -> None """Pause all sounds.""" for name in self._sounds: if not isinstance(self._sounds[name], str): self._sounds[name].pause()
[docs] def print_stats_cache(self): # type: () -> None """ * In standard Python with SimpleGUICS2Pygame: Print to stderr some statistics of cached Pygame surfaces used by each image of this Loader. See `Image._print_stats_cache`_ . * In SimpleGUI of CodeSkulptor: do nothing. .. _`Image._print_stats_cache`: simpleguics2pygame/image.html#SimpleGUICS2Pygame.simpleguics2pygame.image.Image._print_stats_cache """ # noqa if Loader.__SIMPLEGUICS2PYGAME: max_length = max([len(name) for name in self._images]) for name, image in sorted(self._images.items()): image._print_stats_cache('Loader %s%s' # type: ignore # pylint: disable=protected-access # noqa % (name, ' ' * (max_length - len(name))))
[docs] def wait_loaded(self): # type: () -> None """ Draw a progression bar on canvas and wait until all images and sounds are fully loaded. Then execute `self._after_function`. After `self._max_waiting` milliseconds, abort and execute `self._after_function`. See details in `get_nb_sounds_loaded()` documentation. """ if (((self.get_nb_images_loaded() == self.get_nb_images()) and (self.get_nb_sounds_loaded() == self.get_nb_sounds())) or (self._max_waiting <= 0)): self._after_function() return def check_if_loaded(): """ If all images and sounds are loaded then stop waiting and execute `self._after_function`. """ self.__max_waiting_remain -= Loader._interval if (((self.get_nb_images_loaded() == self.get_nb_images()) and (self.get_nb_sounds_loaded() == self.get_nb_sounds())) or (self.__max_waiting_remain <= 0)): self.__max_waiting_remain = 0 self.__timer.stop() self._frame.set_draw_handler(lambda canvas: None) del self.__timer self._after_function() self.__max_waiting_remain_started = True self.__max_waiting_remain = self._max_waiting if Loader.__SIMPLEGUICS2PYGAME: from SimpleGUICS2Pygame.simpleguics2pygame import create_timer # pylint: disable=import-outside-toplevel # noqa else: from simplegui import create_timer # type: ignore # pytype: disable=import-error # pylint: disable=import-outside-toplevel,import-error # noqa self._frame.set_draw_handler(self._draw_loading) self.__timer = create_timer(Loader._interval, check_if_loaded) self.__timer.start()