from typing import Union, Tuple, List
from abc import ABC, abstractmethod
import miniworlds
import miniworlds.appearances.appearance as appearance_mod
import miniworlds.appearances.costume as costume
import miniworlds.base.exceptions as miniworlds_exception
from miniworlds.base.exceptions import MiniworldsError
import pygame
[Doku]
class AppearancesManager(ABC):
def __init__(self, parent):
self.appearances_list = []
self.parent = parent
self.appearance = None
self._rect = None
self.is_animated = None
self.animation_speed = 10
self.is_upscaled = None
self.is_scaled = None
self.is_scaled_to_width = None
self.is_scaled_to_height = None
self.has_appearance = False
self._iter_index = 0
self._is_display_initialized = False
# defaults
self._border = None
def _init_display(self):
if not self._is_display_initialized:
self._is_display_initialized = True
self.appearance.set_dirty("all", self.appearance.LOAD_NEW_IMAGE)
@property
def image(self) -> pygame.Surface:
if self.appearance:
return self.appearance.image
else:
return pygame.Surface((1, 1))
def _create_appearance_from_source(self, source) -> "appearance_mod.Appearance":
if isinstance(source, costume.Costume):
return source
if source is None:
appearance = self.create_appearance()
elif type(source) in [str, pygame.Surface, tuple]:
appearance = self.create_appearance()
appearance.add_image(source)
else:
raise MiniworldsError(
f"Wrong type in _create_appearance_from_source got {type(source)}",
)
return appearance
[Doku]
def add_new_appearance(
self, source: Union[str, pygame.Surface, "costume.Costume", Tuple, None]
) -> "appearance_mod.Appearance":
"""Adds a new Appearance (costume or background) to manager.
called by ``add_costume`` and ``add_background`` in subclasses.
"""
appearance: "appearance_mod.Appearance" = self._create_appearance_from_source(
source
)
if not self.has_appearance and source:
self.has_appearance = True
return self._add_first_appearance(appearance)
elif not self.has_appearance and not source:
self.has_appearance = False
return self._add_default_appearance()
elif source:
return self._add_appearance_to_manager(appearance)
else:
raise MiniworldsError(
f"""Error: Got wrong type for appearance.
Expected: str, pygame.Surface, Costume, tuple; got {type(source)}, {source}"""
)
[Doku]
def set_new_appearance(
self, source: Union[str, pygame.Surface, "costume.Costume", Tuple, None]
):
if not self.has_appearance:
return self.add_new_appearance(source)
else:
self.remove_appearance()
self.add_new_appearance(source)
[Doku]
def add_new_appearances(self, sources: List) -> None:
if type(sources) in [list]:
for appearance in sources:
self.add_new_appearance(appearance)
else:
raise MiniworldsError(f"Appearances must be list, got {type(sources)}")
[Doku]
def add_new_appearance_from_list(
self, sources: List
) -> "appearance_mod.Appearance":
head = sources[0]
tail = sources[1:]
appearance = self.add_new_appearance(head)
for source in tail:
appearance.add_image(source)
return appearance
[Doku]
@abstractmethod
def create_appearance(self) -> "appearance_mod.Appearance":
"""Returns a new appearance (Background instance or Costume instance)"""
pass
def _add_default_appearance(self) -> "appearance_mod.Appearance":
appearance = self.create_appearance()
return self._add_first_appearance(appearance)
def _add_first_appearance(
self, appearance: "appearance_mod.Appearance"
) -> "appearance_mod.Appearance":
self.appearances_list = []
self._add_appearance_to_manager(appearance)
return appearance
def _add_appearance_to_manager(
self, appearance: "appearance_mod.Appearance"
) -> "appearance_mod.Appearance":
self.appearance = appearance
self.appearances_list.append(appearance)
self._set_appearance_defaults()
self.appearance.set_dirty("all", self.appearance.LOAD_NEW_IMAGE)
return appearance
def _set_appearance_defaults(self):
self.appearance._set_defaults(
is_animated=self.is_animated,
animation_speed=self.animation_speed,
is_upscaled=self.is_upscaled,
is_scaled_to_width=self.is_scaled_to_width,
is_scaled_to_height=self.is_scaled_to_height,
is_scaled=self.is_scaled,
border=self.border,
)
[Doku]
def next_appearance(self) -> "appearance_mod.Appearance":
"""Switches to next appearance
Returns:
appearance_mod.Appearance: the switched appearance
"""
index = self.find_appearance(self.appearance)
if index < self.length() - 1:
index += 1
else:
index = 0
return self.switch_appearance(index)
[Doku]
def length(self) -> int:
"""Number of appearance in appearance manager
Returns:
int: _description_
"""
if self.has_appearance:
return len(self.appearances_list)
else:
return 0
def __len__(self) -> int:
return self.length()
[Doku]
def get_appearance_at_index(
self, index: int
) -> Union["appearance_mod.Appearance", None]:
if 0 <= index < len(self.appearances_list):
return self.appearances_list[index]
else:
return None
[Doku]
def find_appearance(self, appearance: "appearance_mod.Appearance") -> int:
"""Searches for appearance; returns index of appearance
Returns:
int: Index of found appearance; -1 if appearance was not found.
"""
for index, a_appearance in enumerate(self.appearances_list):
if a_appearance == appearance:
return index
return -1
def _set_all(self, attribute, value):
"""Sets attribute for all appearance in manager."""
for appearance in self.appearances_list:
setattr(appearance, attribute, value)
[Doku]
def set_border(self, value):
self._border = value
self._set_all("border", value)
[Doku]
def set_animated(self, value):
self.is_animated = value
self._set_all("is_animated", value)
[Doku]
def set_animation_speed(self, value):
self.animation_speed = value
self._set_all("animation_speed", value)
[Doku]
def set_upscaled(self, value):
self.is_upscaled = value
self._set_all("is_upscaled", value)
[Doku]
def set_scaled_to_width(self, value):
self.is_scaled_to_width = value
self._set_all("is_scaled_to_width", value)
[Doku]
def set_scaled_to_height(self, value):
self.is_scaled_to_height = value
self._set_all("is_scaled_to_height", value)
[Doku]
def set_scaled(self, value):
self.is_scaled = value
self._set_all("is_scaled", value)
[Doku]
def list(self) -> List[appearance_mod.Appearance]:
"""Returns all appearances in manager as list.
Returns:
List[appearance_mod.Appearance]: All appearances in manager as list
"""
return self.appearances_list
def __str__(self):
return f"#Appearance-Manager : {str(len(self.appearances_list))} Appearances: {str(self.appearances_list)}, ID: {self.__hash__()}#"
def _remove_appearance_from_manager(self, appearance: "appearance_mod.Appearance"):
"""Removes appearance from manager
If self.length == 1, the last costume is removed and a default appearance will be added.
Args:
appearance (appearance_mod.Appearance): _description_
Returns:
bool: True, if an appearance was removed
"""
if self.has_appearance and self.length() > 0:
if appearance == self.appearance:
if self.length() == 1:
self.appearances_list.remove(appearance)
del appearance
self._add_default_appearance()
self.has_appearance = False
return True
else:
first_appearance = self.get_appearance_at_index(0)
self.switch_appearance(first_appearance)
self.appearances_list.remove(appearance)
del appearance
return True
return False
[Doku]
def remove_appearance(self, source: Union[int, "appearance_mod.Appearance"] = -1):
"""Removes an appearance (costume or background) from manager
Defaults:
Removes last costume.
Args:
source: The index of the new appearance or the Appearance which should be removed Defaults to -1
(last costume)
"""
if isinstance(source, int):
source = self.get_appearance_at_index(source)
if source and isinstance(source, appearance_mod.Appearance):
return self._remove_appearance_from_manager(source)
else:
raise MiniworldsError(
f"Expected type int or Appearance (Costume or Background), got {type(source)}"
)
[Doku]
def reset(self):
for appearance in self.appearances_list:
self.remove_appearance(appearance)
[Doku]
def switch_appearance(
self, source: Union[int, "appearance_mod.Appearance"]
) -> "appearance_mod.Appearance":
if isinstance(source, int):
if source >= self.length():
raise miniworlds_exception.CostumeOutOfBoundsError(
self.parent, self.length(), source
)
new_appearance = self.get_appearance_at_index(source)
elif isinstance(source, appearance_mod.Appearance) or isinstance(
source, miniworlds.Appearance
):
new_appearance = source
else:
raise MiniworldsError(
f"Wrong type in switch_appearance, got {type(source)}"
)
self.appearance = new_appearance
self.appearance.image_manager.end_animation(new_appearance)
self.appearance.set_image(0)
self.appearance.set_dirty("all", self.appearance.LOAD_NEW_IMAGE)
return self.appearance
[Doku]
def animate(self, speed: int):
self.appearance.animation_speed = speed
self.appearance.animate()
[Doku]
def animate_appearance(self, appearance: "appearance_mod.Appearance", speed: int):
if appearance is None:
raise miniworlds_exception.CostumeIsNoneError()
self.switch_appearance(appearance)
self.appearance.animation_speed = speed
self.appearance.animate()
[Doku]
def self_remove(self) -> None:
"""Implemented in subclasses"""
pass
def __iter__(self):
self._iter_index = 0
return self
def __next__(self):
if self._iter_index < len(self.appearances_list):
appearance_at_position = self.get_appearance_at_index(self._iter_index)
self._iter_index += 1
return appearance_at_position
else:
raise StopIteration
@property
def orientation(self):
return [appearance.orientation for appearance in self.appearances_list]
@orientation.setter
def orientation(self, value):
for appearance in self.appearances_list:
appearance.orientation = value
@property
def animation_speed(self):
return self.appearance.animation_speed
@animation_speed.setter
def animation_speed(self, value):
for appearance in self.appearances_list:
appearance.animation_speed = value
@property
def border(self):
return self._border
@border.setter
def border(self, value):
self._border = value
for appearance in self.appearances_list:
appearance.border = value
[Doku]
def get_actual_appearance(self) -> "appearance_mod.Appearance":
if not self.appearance:
self._add_default_appearance()
return self.appearance