diff --git a/images/sprites/misc/gif_cyberpunk.gif b/images/sprites/misc/gif_cyberpunk.gif new file mode 100644 index 0000000..34050bb Binary files /dev/null and b/images/sprites/misc/gif_cyberpunk.gif differ diff --git a/wideboy/entities.py b/wideboy/entities.py index 6b911ad..fbcc082 100644 --- a/wideboy/entities.py +++ b/wideboy/entities.py @@ -117,3 +117,8 @@ class WidgetVinyl(ComFrame, ComFade, ComMotion, ComAlpha, ComVisible): @entity class WidgetGalaxy(ComFrame, ComFade, ComMotion, ComAlpha, ComVisible): pass + + +@entity +class WidgetAnimatedGif(ComFrame, ComFade, ComMotion, ComAlpha, ComVisible): + pass diff --git a/wideboy/sprites/graphics.py b/wideboy/sprites/graphics.py index 98247e1..932f494 100644 --- a/wideboy/sprites/graphics.py +++ b/wideboy/sprites/graphics.py @@ -15,8 +15,28 @@ def load_image(filename: str, convert_alpha: bool = True) -> Surface: return image -def pil_to_surface(image: Image.Image) -> Surface: - return pygame.image.fromstring(image.tobytes(), image.size, image.mode).convert_alpha() # type: ignore +def load_gif(filename: str, convert_alpha: bool = True) -> list[Surface]: + gif_image = Image.open(filename) + frames = [] + try: + while True: + gif_image.seek(gif_image.tell() + 1) + gif_frame = gif_image.copy() + if gif_frame: + frame = pil_to_surface(gif_frame, convert_alpha) + frames.append(frame) + except EOFError: + pass + return frames + + +def pil_to_surface(image: Image.Image, convert_alpha: bool = True) -> Surface: + if image.mode not in ["RGB", "RGBA"]: + image = image.convert("RGBA") + surface = pygame.image.fromstring(image.tobytes(), image.size, "RGBA") + if convert_alpha: + surface = surface.convert_alpha() # type: ignore + return surface def surface_to_pil(surface: Surface) -> Image.Image: diff --git a/wideboy/systems/preprocess.py b/wideboy/systems/preprocess.py index 6689fd3..d35ab12 100644 --- a/wideboy/systems/preprocess.py +++ b/wideboy/systems/preprocess.py @@ -4,7 +4,7 @@ from pygame.display import Info as DisplayInfo from typing import Callable, Generator, List, Tuple from ..entities import AppState, Cache, WidgetSysMessage -from ..sprites.graphics import load_image +from ..sprites.graphics import load_image, load_gif from .scene.sprites import build_mode7_sprite, build_system_message_sprite logger = logging.getLogger(__name__) @@ -38,6 +38,14 @@ def preprocess_load_image(cache: Cache, key: str, path: str): cache.surfaces[key].append(load_image(path)) +def preprocess_load_gif(cache: Cache, key: str, path: str): + logger.debug(f"preprocess_load_gif: key={key} path={path}") + if key not in cache.surfaces: + cache.surfaces[key] = [] + surfaces = load_gif(path) + cache.surfaces[key] = surfaces + + def preprocess_text(cache: Cache, key: str, text: str): logger.debug(f"preprocess_text: key={key} text={text}") sprite = build_system_message_sprite(text) @@ -139,6 +147,7 @@ def tasks(self) -> Generator: 0.8, ) yield f"Vinyl #1 [{r/360*100:.0f}%]" + # Mode7 Milky Way surface = load_image( f"{self.app_state.config.paths.images_sprites}/misc/milky_way.png" ) @@ -156,3 +165,10 @@ def tasks(self) -> Generator: 0.6, ) yield f"Milky Way [{r/360*100:.0f}%]" + # Animated GIF Test + preprocess_load_gif( + self.cache, + "gif_test", + f"{self.app_state.config.paths.images_sprites}/misc/gif_cyberpunk.gif", + ) + yield "Animated GIF Test" diff --git a/wideboy/systems/scene/__init__.py b/wideboy/systems/scene/__init__.py index 99905e6..73a07cf 100644 --- a/wideboy/systems/scene/__init__.py +++ b/wideboy/systems/scene/__init__.py @@ -18,6 +18,7 @@ from .entity_tiles import CELLS from .stages import Stage from .stages.boot import StageBoot +from .stages.city import StageCity from .stages.default import StageDefault from .stages.galaxy import StageGalaxy from .stages.vinyl import StageVinyl @@ -157,7 +158,7 @@ def _handle_scene_mode_change(self) -> None: ) ) else: - # Galaxy Mode + # Galaxy Stage if self.scene_mode == "galaxy": logger.info("GALAXY MODE") self._switch_stage( @@ -166,7 +167,7 @@ def _handle_scene_mode_change(self) -> None: (self.display_info.current_w, self.display_info.current_h), ) ) - # Vinyl Mode + # Vinyl Stage elif self.scene_mode == "vinyl": logger.info("VINYL MODE") self._switch_stage( @@ -175,7 +176,16 @@ def _handle_scene_mode_change(self) -> None: (self.display_info.current_w, self.display_info.current_h), ) ) - # Default Mode + # City Stage + elif self.scene_mode == "city": + logger.info("CITY MODE") + self._switch_stage( + StageCity( + self.entities, + (self.display_info.current_w, self.display_info.current_h), + ) + ) + # Default Stage else: logger.info("DEFAULT MODE") self._switch_stage( diff --git a/wideboy/systems/scene/hass_entities.py b/wideboy/systems/scene/hass_entities.py index 53cebf3..d1da684 100644 --- a/wideboy/systems/scene/hass_entities.py +++ b/wideboy/systems/scene/hass_entities.py @@ -57,7 +57,7 @@ class ModeSelect(SelectEntity): description: str = "Mode" initial_state: str = "default" options: Dict[str, Any] = { - "options": ["default", "galaxy", "vinyl"], + "options": ["city", "default", "galaxy", "vinyl"], } def callback( diff --git a/wideboy/systems/scene/stages/city.py b/wideboy/systems/scene/stages/city.py new file mode 100644 index 0000000..ca4b8a4 --- /dev/null +++ b/wideboy/systems/scene/stages/city.py @@ -0,0 +1,47 @@ +import logging +from ecs_pattern import EntityManager +from typing import Tuple +from ....entities import ( + Cache, + WidgetAnimatedGif, + WidgetClockDate, + WidgetClockTime, + WidgetTileGrid, +) +from ..sprites import build_image_sprite +from . import Stage + +logger = logging.getLogger(__name__) + + +class StageCity(Stage): + def __init__( + self, + entities: EntityManager, + display_size: Tuple[int, int], + ) -> None: + super().__init__(entities) + self.display_size = display_size + self.setup() + + def setup(self) -> None: + self.cache = next(self.entities.get_by_class(Cache)) + + for i in range(3): + self.stage_entities.append( + WidgetAnimatedGif( + build_image_sprite(self.cache.surfaces["gif_test"][0]), + x=i * 300, + y=-20, + z_order=5, + frames=self.cache.surfaces["gif_test"], + frame_delay=1, + ), # type: ignore[call-arg] + ) + + for w in self.entities.get_by_class( + WidgetClockDate, + WidgetClockTime, + WidgetTileGrid, + ): + w.fade_target_alpha = 255