commented widgets

This commit is contained in:
Faraphel 2023-03-14 23:09:03 +01:00
parent 89c6b81ba9
commit 51eb35c43c
16 changed files with 206 additions and 132 deletions

View file

@ -6,5 +6,9 @@ path = path_sound / "ambient"
class SoundAmbient(MediaGroup):
"""
Groupe contenant les sons ambient du jeu.
"""
menu = Sound(path / "menu.wav")
sea = Sound(path / "sea.wav")

View file

@ -6,6 +6,10 @@ path = path_sound / "effect"
class SoundEffect(MediaGroup):
"""
Groupe contenant les effets sonores du jeu.
"""
placed = Sound(path / "placed.wav")
touched = Sound(path / "touched.wav")
missed = Sound(path / "missed.wav")

View file

@ -5,7 +5,7 @@ import pyglet
class MediaGroup(ABC):
"""
This class represent a music group that can be played.
Cette classe représente un groupe de musique pouvant être joué.
"""
player: pyglet.media.Player

View file

@ -11,6 +11,10 @@ if TYPE_CHECKING:
class Sound(Media):
"""
Représente un son pouvant être joué par le lecteur.
"""
def __init__(self, path: Path):
self.path = path

View file

@ -10,10 +10,21 @@ if TYPE_CHECKING:
class Media(ABC):
loaded_media: dict[Path, pyglet.media.Source] = {}
"""
Représente un type de média
"""
loaded_media: dict[Path, pyglet.media.Source] = {} # cache des médias chargés
@classmethod
def get_media(cls, path: Path, owner: "MediaGroup") -> pyglet.media.Source:
"""
Renvoie le média correspondant au chemin donné
:param path: le chemin du media
:param owner: la classe qui a appelé la fonction
:return: le média
"""
if (media := cls.loaded_media.get(path)) is None:
# charge le son
media = pyglet.media.load(path)
@ -38,4 +49,10 @@ class Media(ABC):
@abstractmethod
def __get__(self, instance, owner) -> pyglet.media.Source:
"""
Renvoie le média correspondant à l'instance donnée
:param instance: instance de la classe qui a appelé la fonction
:param owner: classe ayant appelé la fonction
:return: le media
"""
pass

View file

@ -14,8 +14,9 @@ if TYPE_CHECKING:
class Button(BoxWidget):
"""
A button widget with a background texture that change depending on if it is clicked or hovered, and a label.
You can pass parameter to the background and label by adding "background_" and "label_" before the parameter.
Un bouton avec une texture de fond qui change en fonction de s'il est cliqué ou survolé et un label.
Vous pouvez passer des paramètres pour le background et au label en ajoutant "background_" et "label_"
devant le paramètre.
"""
def __init__(self, scene: "Scene",
@ -48,17 +49,17 @@ class Button(BoxWidget):
self.add_listener("on_hover_change", lambda *_: self._refresh_background())
self.add_listener("on_click_change", lambda *_: self._refresh_background())
self._refresh_size() # refresh the size and position for the background and label
self._refresh_size() # rafraîchit la taille et la position du background et du label
# background
@property
def background_texture(self) -> pyglet.image.AbstractImage:
"""
Return the correct texture for the background.
The clicking texture per default, if hover the hovered texture (if it exists)
and if click the clicking texture (if it exists)
:return: the corresponding texture
Renvoie la bonne texture pour le fond.
Utilise la texture normale par défaut, si survolé la texture de survol (si elle existe)
et la texture de clic (si elle existe) si cliqué
:return: la texture correspondante
"""
return (

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class Checkbox(BoxWidget):
"""
A checkbox widget with a background texture that change depending on if it is checked or unchecked.
Un widget de checkbox avec une texture d'arrière-plan qui change en fonction de si elle est cochée ou non.
"""
def __init__(self, scene: "Scene",
@ -43,7 +43,7 @@ class Checkbox(BoxWidget):
self._refresh_size()
# refreshing
# rafraichissement
@property
def tick_texture(self):
@ -56,7 +56,7 @@ class Checkbox(BoxWidget):
self.tick.x, self.tick.y = self.xy
self.tick.width, self.tick.height = self.size
# property
# propriétés
@property
def state(self):

View file

@ -19,7 +19,7 @@ if TYPE_CHECKING:
class GameGrid(BoxWidget):
"""
A widget that represent a game grid.
Un widget représentant la grille du jeu
"""
def __init__(self, scene: "Scene",
@ -49,7 +49,7 @@ class GameGrid(BoxWidget):
self.group_cursor = pyglet.graphics.Group(order=1)
self.group_line = pyglet.graphics.Group(order=2)
# the list of the size of the boats to place
# la liste des tailles des bateaux a placé sur la grille
self.boats_length = [] if boats_length is None else sorted(boats_length, reverse=True)
# créer la planche du jeu
@ -104,13 +104,13 @@ class GameGrid(BoxWidget):
int((rel_y-1) / self.cell_height)
)
# refresh
# rafraichissement
def _refresh_size(self):
self.background.x, self.background.y = self.xy
self.background.width, self.background.height = self.size
# lines
# lignes
for column, line in enumerate(self.lines[:self.columns - 1], start=1):
line.x = self.x + self.cell_width * column
@ -149,7 +149,7 @@ class GameGrid(BoxWidget):
self.cursor.y = self.y + cell_y * self.height / self.rows
self.cursor.width, self.cursor.height = self.cell_size
self.preview_boat((cell_x, cell_y)) # display the preview of the boat on this cell
self.preview_boat((cell_x, cell_y)) # affiche la grille du jeu en prévisualisant cette cellule
# function
@ -161,7 +161,11 @@ class GameGrid(BoxWidget):
self.display_board(self.board)
def display_board(self, board: Board, preview: bool = False):
# remplacer par l'utilisation de board.boats ?
"""
Affiche la grille du jeu.
:param board: la grille du jeu à afficher
:param preview: la prévisualisation du dernier bateau est-elle activée ?
"""
matrice = board.boats
max_boat: int = np.max(matrice)
@ -220,12 +224,21 @@ class GameGrid(BoxWidget):
self._refresh_size()
def swap_orientation(self):
"""
Inverse l'orientation du bateau en cours de placement.
"""
self.orientation = (
Orientation.HORIZONTAL if self.orientation is Orientation.VERTICAL else
Orientation.VERTICAL
)
def place_boat(self, cell: Point2D):
"""
Place un bateau sur la grille du jeu.
:param cell: position sur laquelle placer le bateau
"""
if len(self.boats_length) == 0: return
try:
@ -234,10 +247,10 @@ class GameGrid(BoxWidget):
cell
)
except InvalidBoatPosition:
pass # if the boat can't be placed, ignore
pass # si le bateau n'a pas pu être placé, ignore
else: # if the boat have been placed
self.boats_length.pop(0) # remove the boat from the list of boat to place
else: # si le bateau a bien été placé
self.boats_length.pop(0) # retire la taille du bateau de la liste des bateaux à placer
self.trigger_event("on_boat_placed")
if len(self.boats_length) == 0:
@ -246,6 +259,11 @@ class GameGrid(BoxWidget):
self.refresh_board() # rafraichi l'affichage
def preview_boat(self, cell: Point2D):
"""
Prévisualise le prochain bateau à placer.
:param cell: position visualiser le bateau
"""
if len(self.boats_length) == 0: return
try:
@ -260,6 +278,13 @@ class GameGrid(BoxWidget):
else: self.display_board(preview_board, preview=True)
def place_bomb(self, cell: Point2D, force_touched: bool = None) -> BombState:
"""
Place une bombe sur la grille du jeu.
:param cell: cellule sur laquelle placer la bombe
:param force_touched: la cellule doit-elle forcer l'affichage comment étant manqué ou touché ?
:return: l'état de la bombe
"""
bomb_state = self.board.bomb(cell)
if force_touched is not None:
@ -270,7 +295,10 @@ class GameGrid(BoxWidget):
return bomb_state
def remove_bomb(self, cell: Point2D):
# retire une bombe de la planche
"""
Retire une bombe de la grille du jeu.
:param cell: cellule de la bombe à retirer
"""
self.board.remove_bomb(cell)
self.refresh_board()
@ -278,29 +306,27 @@ class GameGrid(BoxWidget):
cell = self.get_cell_from_rel(rel_x, rel_y)
match button:
# si le joueur fait un clic droit, inverse l'orientation du bateau en cours de placement
case pyglet.window.mouse.RIGHT:
self.swap_orientation()
self.preview_boat(cell)
# si le joueur fait un clic gauche, place un bateau ou une bombe
case pyglet.window.mouse.LEFT:
self.place_boat(cell)
self.trigger_event("on_request_place_bomb", cell)
# property
# propriétés
@property
def cell_width(self) -> float:
return self.width / self.columns
def cell_width(self) -> float: return self.width / self.columns
@property
def cell_height(self) -> float:
return self.height / self.rows
def cell_height(self) -> float: return self.height / self.rows
@property
def cell_size(self) -> tuple[float, float]:
return self.cell_width, self.cell_height
def cell_size(self) -> tuple[float, float]: return self.cell_width, self.cell_height
# event
# événements
def on_resize(self, width: int, height: int):
self._refresh_size()
def on_resize(self, width: int, height: int): self._refresh_size()

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class Image(BoxWidget):
"""
An image widget with a texture.
Un widget d'image avec une texture.
"""
def __init__(self, scene: "Scene",

View file

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class Input(BoxWidget):
"""
An input widget with a background texture and a label. A regex pattern can be added to validate the input.
Un widget d'entrée avec une texture de fond et un label. Des paternes regex peut être ajouté pour valider l'entrée.
"""
def __init__(self, scene: "Scene",
@ -64,18 +64,19 @@ class Input(BoxWidget):
@property
def background_texture(self) -> pyglet.image.AbstractImage:
"""
Return the correct texture for the background.
The clicking texture per default, if hover the hovered texture (if it exists)
and if click the clicking texture (if it exists)
:return: the corresponding texture
Renvoie la texture de fond correspondante.
Si le widget est activé, renvoie la texture active (si elle existe),
la texture de clic (si elle existe) sinon la texture normale
:return: la texture de fond correspondante
"""
return (
texture if self.activated and (texture := self.style.get("active")) is not None else # NOQA
texture if not self.valid and (texture := self.style.get("error")) is not None else
self.style.get("normal")
)
# refresh
# rafraichissement
def _refresh_background(self) -> None:
self.background.image = self.background_texture
@ -94,8 +95,7 @@ class Input(BoxWidget):
# property
@property
def valid(self):
return self._valid
def valid(self): return self._valid
@valid.setter
def valid(self, valid: bool):
@ -103,14 +103,12 @@ class Input(BoxWidget):
self._refresh_background()
@property
def text(self):
return self.label.text
def text(self): return self.label.text
@text.setter
def text(self, text: str):
self.label.text = text
def text(self, text: str): self.label.text = text
# event
# événements
def on_key_press(self, symbol: int, modifiers: int):
if not self.activated: return # ignore si ce widget est désactivé / non sélectionné

View file

@ -12,8 +12,7 @@ if TYPE_CHECKING:
class Scroller(BoxWidget):
"""
A scroller widget with a background texture, a scroller and a label.
The cursor can be moved between the "from" and the "to" value
Un widget qui affiche un curseur qui peut être déplacé entre deux valeurs.
"""
def __init__(self, scene: "Scene",
@ -64,7 +63,7 @@ class Scroller(BoxWidget):
self._to = to
self.value = value
# refresh
# rafraichissement
def _refresh(self):
# background
@ -91,7 +90,7 @@ class Scroller(BoxWidget):
def _refresh_cursor(self, rel_x: int):
self.value = (rel_x / self.width) * (self.to - self.from_) + self.from_
# property
# propriétés
@property
def value(self):
@ -122,7 +121,7 @@ class Scroller(BoxWidget):
self._to = to
self._refresh()
# event
# événements
def on_resize(self, width: int, height: int):
self._refresh()

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
class Text(BoxWidget):
"""
A widget that display a text
Un widget qui affiche du texte
"""
def __init__(self, scene: "Scene",

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
class BoxWidget(Widget, ABC):
"""
Same as a basic widget, but inside a box
Pareil qu'un Widget, mais dans une boîte de collision (bbox)
"""
def __init__(self, scene: "Scene",
@ -21,23 +21,23 @@ class BoxWidget(Widget, ABC):
height: Distance = None):
super().__init__(scene)
# memorize the value with a percent value
# Défini les bordures de la boîte de collision. Peut utiliser des nombres ou des unités de distance
self.x = x
self.y = y
self.width = width
self.height = height
self.hovering = False # is the button currently hovered ?
self.clicking = False # is the button currently clicked ?
self.activated = False # is the button activated ? (the last click was inside this widget)
self.hovering = False # La bbox est-elle actuellement survolée ?
self.clicking = False # La bbox est-elle actuellement cliqué ?
self.activated = False # La bbox est-il actuellement activé ? (le dernier clic a été à l'intérieur)
# property
# propriétés
def _getter_distance(self, raw_distance: Distance) -> int:
"""
Return the true distance in pixel from a more abstract distance
:param raw_distance: the distance object to convert to pixel
:return: the true distance in pixel
Renvoie la distance en pixel d'une distance abstraite
:param raw_distance: la distance à convertir en pixel
:return: la vrai distance en pixel
"""
if isinstance(raw_distance, int): return raw_distance
@ -47,88 +47,71 @@ class BoxWidget(Widget, ABC):
raise TypeError(f"Invalid type for the distance : {type(raw_distance)}")
@property
def x(self) -> int:
return self._getter_distance(self._x)
def x(self) -> int: return self._getter_distance(self._x)
@x.setter
def x(self, x: Distance):
self._x = x
def x(self, x: Distance): self._x = x
@property
def y(self) -> int:
return self._getter_distance(self._y)
def y(self) -> int: return self._getter_distance(self._y)
@y.setter
def y(self, y: Distance):
self._y = y
def y(self, y: Distance): self._y = y
@property
def xy(self) -> tuple[int, int]:
return self.x, self.y
def xy(self) -> tuple[int, int]: return self.x, self.y
@property
def x2(self) -> int:
return self.x + self.width
def x2(self) -> int: return self.x + self.width
@property
def y2(self) -> int:
return self.y + self.height
def y2(self) -> int: return self.y + self.height
@property
def xy2(self) -> tuple[int, int]:
return self.x2, self.y2
def xy2(self) -> tuple[int, int]: return self.x2, self.y2
@property
def width(self) -> int:
return self._getter_distance(self._width)
def width(self) -> int: return self._getter_distance(self._width)
@width.setter
def width(self, width: Optional[Distance]):
self._width = width
def width(self, width: Optional[Distance]): self._width = width
@property
def height(self) -> int:
return self._getter_distance(self._height)
def height(self) -> int: return self._getter_distance(self._height)
@height.setter
def height(self, height: Optional[Distance]):
self._height = height
def height(self, height: Optional[Distance]): self._height = height
@property
def size(self) -> tuple[int, int]:
return self.width, self.height
def size(self) -> tuple[int, int]: return self.width, self.height
@property
def bbox(self) -> tuple[int, int, int, int]:
return self.x, self.y, self.x2, self.y2
def bbox(self) -> tuple[int, int, int, int]: return self.x, self.y, self.x2, self.y2
@property
def center_x(self) -> float:
return self.x + (self.width / 2)
def center_x(self) -> float: return self.x + (self.width / 2)
@property
def center_y(self) -> float:
return self.y + (self.height / 2)
def center_y(self) -> float: return self.y + (self.height / 2)
@property
def center(self) -> tuple[float, float]:
return self.center_x, self.center_y
def center(self) -> tuple[float, float]: return self.center_x, self.center_y
# function
# fonctions
def in_bbox(self, point: Point2D) -> bool:
return in_bbox(point, self.bbox)
# event
# événements
def on_mouse_motion(self, x: int, y: int, dx: int, dy: int):
"""
When the mouse is moved, this event is triggered.
Allow the implementation of the on_hover_enter and on_hover_leave events
:x: the x position of the mouse
:y: the y position of the mouse
:dx: the difference of the x mouse axis
:dy: the difference of the y mouse axis
Lorsque la souris est déplacée, cet événement est déclenché.
Permet d'implémenter les événements on_hover, on_hover_enter et on_hover_leave
:param x: la position x de la souris
:param y: la position y de la souris
:param dx: la différence de la position x de la souris
:param dy: la différence de la position y de la souris
"""
rel_x, rel_y = x - self.x, y - self.y
@ -136,39 +119,59 @@ class BoxWidget(Widget, ABC):
old_hovering = self.hovering
self.hovering = self.in_bbox((x, y))
if old_hovering != self.hovering: # if the hover changed
# call the hover changed event
if old_hovering != self.hovering: # si le survole a changé d'état
# appelle d'événement on_hover_change
self.trigger_event("on_hover_change", rel_x, rel_y)
# call the hover enter / leave event
# appelle l'événement on_hover_enter ou on_hover_leave selon la valeur
self.trigger_event("on_hover_enter" if self.hovering else "on_hover_leave", rel_x, rel_y)
if self.hovering: # if the mouse motion is inside the collision
self.trigger_event("on_hover", rel_x, rel_y) # call the hover event
if self.hovering: # si la souris est dans la bbox actuellement
self.trigger_event("on_hover", rel_x, rel_y) # appelle l'événement on_hover
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int):
"""
Lorsque la souris est cliqué, cet événement est déclenché.
:param x: la position x de la souris
:param y: la position y de la souris
:param button: button de la souris cliqué
:param modifiers: modificateur du bouton de la souris
"""
rel_x, rel_y = x - self.x, y - self.y
self.activated = self.in_bbox((x, y))
self.trigger_event("on_activate_change", rel_x, rel_y, button, modifiers)
if self.activated: # if the click was inside the widget
if self.activated: # si le clic s'est produit dans la bbox
# appel des événements on_activate_enter
self.trigger_event("on_activate_enter", rel_x, rel_y, button, modifiers)
self.clicking = True # the widget is also now clicked
self.clicking = True # défini le widget comme étant à présent cliqué
# appel des événements on_click_change et on_click_press
self.trigger_event("on_click_change", rel_x, rel_y, button, modifiers)
self.trigger_event("on_click_press", rel_x, rel_y, button, modifiers)
else:
# si le clic n'était pas dans la bbox, appel de l'événement on_activate_leave
self.trigger_event("on_activate_leave", rel_x, rel_y, button, modifiers)
def on_mouse_release(self, x: int, y: int, button: int, modifiers: int):
"""
Lorsque le clic de la souris est relâché, cet événement est déclenché.
:param x: la position x de la souris
:param y: la position y de la souris
:param button: button de la souris cliqué
:param modifiers: modificateur du bouton de la souris
"""
rel_x, rel_y = x - self.x, y - self.y
old_click: bool = self.clicking
self.clicking = False # the widget is no longer clicked
self.clicking = False # le widget n'est plus cliqué
if not self.in_bbox((x, y)): return # if the release was not in the collision, ignore
if not self.in_bbox((x, y)): return # si le clic n'a pas été relâché dans la bbox, ignore
if old_click: # if this button was the one hovered when the click was pressed
if old_click: # si ce bouton était celui qui était survolé lorsque le bouton a été cliqué
# déclenche les événements on_click_change et on_click_release
self.trigger_event("on_click_change", rel_x, rel_y, button, modifiers)
self.trigger_event("on_click_release", rel_x, rel_y, button, modifiers)

View file

@ -9,9 +9,9 @@ if TYPE_CHECKING:
class Widget(Listener, ABC):
"""
A Widget that can be attached to a scene.
Un widget pouvant être attaché à une scène.
It can react to any "on_" event from the scene.
Il peut réagir à n'importe quel événement "on_" de la scène.
"""
def __init__(self, scene: "Scene", **kwargs):

View file

@ -9,7 +9,7 @@ from source.type import ColorRGBA
class GameWindow(Window): # NOQA
"""
Similar to the normal Window, but add small feature useful for a game like a fps counter.
Similaire à la classe Window, mais ajoute quelque fonctionnalités pratique pour un jeu. (Option, FPS, etc.)
"""
def __init__(self,
@ -34,6 +34,10 @@ class GameWindow(Window): # NOQA
pyglet.clock.schedule_once(lambda *_: self.load_option(), 0)
def load_option(self):
"""
Charge les options depuis le fichier self.option_path.
"""
try:
if self.option_path.exists():
self.option = Option.load(self, self.option_path)
@ -43,4 +47,5 @@ class GameWindow(Window): # NOQA
if self.option is None: self.option = Option(window=self)
def on_draw_after(self):
# après que tous les éléments ont été dessinés, dessiner le compteur de FPS s'il est activé.
if self.fps_enable: self._fps_counter.draw()

View file

@ -12,8 +12,8 @@ if TYPE_CHECKING:
class Window(pyglet.window.Window, EventPropagationMixin): # NOQA
"""
A window. Based on the pyglet window object.
Scene can be added to the window
Une fenêtre basée sur l'objet Window de pyglet.
Des scènes peuvent y être placé.
"""
def __init__(self, *args, **kwargs):
@ -24,12 +24,21 @@ class Window(pyglet.window.Window, EventPropagationMixin): # NOQA
@property
def childs(self):
"""
Renvoie les scènes de la fenêtre. Utilisé pour la propagation d'événements.
:return: les scènes de la fenêtre.
"""
return self._scenes
# FPS
@staticmethod
def get_fps() -> float:
"""
Renvoie le nombre de FPS actuel de la fenêtre.
:return: le nombre de FPS actuel de la fenêtre.
"""
# on récupère la fonction responsable du rafraichissement de la fenêtre
refresh_func = pyglet.app.event_loop._redraw_windows # NOQA
@ -47,6 +56,11 @@ class Window(pyglet.window.Window, EventPropagationMixin): # NOQA
@staticmethod
def set_fps(value: float):
"""
Définit le nombre de FPS de la fenêtre.
:param value: nombre de FPS souhaité
"""
# on récupère la fonction responsable du rafraichissement de la fenêtre
refresh_func = pyglet.app.event_loop._redraw_windows # NOQA
@ -62,24 +76,23 @@ class Window(pyglet.window.Window, EventPropagationMixin): # NOQA
# Scene Managing
def set_scene(self, scene_class: Type["Scene"], *scene_args, **scene_kwargs) -> "Scene":
def set_scene(self, scene_class: Type["Scene"], **scene_kwargs) -> "Scene":
"""
Set the scene of the window.
:scene_class: the class of the scene to add.
:scene_args: args for the creation of the scene object.
:scene_kwargs: kwargs for the creation of the scene object.
:return: the new created scene.
Défini la scène actuelle pour la fenêtre.
:scene_class: la classe de la scène à ajouter
:scene_kwargs: les arguments clés de la scène
:return: la nouvelle scène créée
"""
self.clear_scene()
return self.add_scene(scene_class, *scene_args, **scene_kwargs)
return self.add_scene(scene_class, **scene_kwargs)
def add_scene(self, scene_class: Type["Scene"], priority: int = 0, **scene_kwargs) -> "Scene":
"""
Add a scene of the window.
:scene_class: the class of the scene to add.
:scene_kwargs: kwargs for the creation of the scene object.
:return: the new created scene.
Ajoute une scène à la fenêtre.
:scene_class: la classe de la scène à ajouter
:scene_kwargs: les arguments clés de la scène
:return: la nouvelle scène créée
"""
scene: "Scene" = scene_class(window=self, **scene_kwargs)
@ -88,15 +101,15 @@ class Window(pyglet.window.Window, EventPropagationMixin): # NOQA
def remove_scene(self, scene: "Scene") -> None:
"""
Remove a scene from the window.
:scene: the scene to remove.
Retire une scène spécifique de la fenêtre
:scene: la scène à retirer
"""
self._scenes.remove(scene)
def clear_scene(self) -> None:
"""
Clear the window from all the scenes.
Retire toutes les scènes de la fenêtre.
"""
self._scenes.clear()