diff --git a/NOTE.md b/NOTE.md new file mode 100644 index 0000000..503f05c --- /dev/null +++ b/NOTE.md @@ -0,0 +1,14 @@ +- Connexion entre les joueurs +- Envoie des paramètres à l'autre joueur (largeur longueur du plateau, nombre de bateau et leur taille, ...) +- Phase de placement des bateaux +- Confirmer l'emplacement des bateaux (envoie des données signalant la confirmation) + +- Joueur 1 sélectionne une case (envoie des données à l'autre joueur) +- Joueur 2 envoie la valeur de retour de board.bomb après les données de joueur 1 +- Joueur 2 sélectionne une case +- Joueur 1 envoie la valeur de retour également +... + +- Joueur 1 gagne (envoie des données de victoire à l'autre joueur) +- Joueur 1 et Joueur 2 s'envoie mutuellement leurs plateaux pour voir les cases restantes de l'autre +- Proposition de rejouer ? \ No newline at end of file diff --git a/main.pyw b/main.pyw index 1f2348e..7f81b20 100644 --- a/main.pyw +++ b/main.pyw @@ -1,16 +1,23 @@ import pyglet -from source.gui.scene import MainMenuScene -from source.gui.scene.debug import FPSScene -from source.gui.scene.test import TestButtonScene, TestLabelScene +from source.gui.scene import Scene +from source.gui.widget import Text, FPSDisplay from source.gui.window import Window +# Test Scene + + +class TestScene(Scene): + def __init__(self, window: "Window", *args, **kwargs): + super().__init__(window, *args, **kwargs) + + self.add_widget(FPSDisplay) + self.add_widget(Text, text="Hello World !") + + # Create a new window -window = Window(resizable=True, vsync=True) -# window.add_scene(TestButtonScene()) -# window.add_scene(TestLabelScene()) -window.add_scene(MainMenuScene()) -window.add_scene(FPSScene()) +window = Window(resizable=True, vsync=False) +window.add_scene(TestScene) # Start the event loop pyglet.app.run(interval=0) diff --git a/requirements.txt b/requirements.txt index 5ed99d8..2212473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pyglet -numpy +pyglet==2.0.4 +numpy==1.24.2 diff --git a/source/gui/README.md b/source/gui/README.md index 139e211..1b00464 100644 --- a/source/gui/README.md +++ b/source/gui/README.md @@ -4,5 +4,5 @@ ne sont pas déjà disponible nativement dans cette librairie, tel que : - Les scènes (attachable à une fenêtre afin de changer rapidement de menu / d'interface) - Les widgets (attachable à des scènes afin de rajouter des boutons, des textes, ...) -Ces éléments permettent de plus facilement gérer le redimentionnement de la fenêtre tout en +Ces éléments permettent de plus facilement gérer le redimensionnement de la fenêtre tout en restant suffisament rapide pour ne pas causer de problème de ralentissement. \ No newline at end of file diff --git a/source/gui/__init__.py b/source/gui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/source/gui/scene/MainMenuScene.py b/source/gui/scene/MainMenuScene.py deleted file mode 100644 index b488b3d..0000000 --- a/source/gui/scene/MainMenuScene.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -import pyglet - -from source.gui.scene.abc import AbstractScene -from source.gui.widget import WidgetLabel, WidgetButton - -if TYPE_CHECKING: - from source.gui.window import Window - - -class MainMenuScene(AbstractScene): - def __init__(self): - super().__init__() - self._title: Optional[WidgetLabel] = None - self._room_create: Optional[WidgetButton] = None - self._room_join: Optional[WidgetButton] = None - - def on_window_added(self, window: "Window"): - normal_texture = pyglet.image.load("./assets/image/button/test_button_normal.png") - hover_texture = pyglet.image.load("./assets/image/button/test_button_hover.png") - click_texture = pyglet.image.load("./assets/image/button/test_button_clicking.png") - - self.button_atlas = pyglet.image.atlas.TextureAtlas() - normal_region = self.button_atlas.add(normal_texture) - hover_region = self.button_atlas.add(hover_texture) - click_region = self.button_atlas.add(click_texture) - - self.background_batch = pyglet.graphics.Batch() - self.label_batch = pyglet.graphics.Batch() - - self._title = WidgetLabel( - x=0.1, y=0.9, width=0.2, height=0.1, - text="Bataille Navale", - font_size=30, - label_batch=self.label_batch, - ) - self.add_widget(self._title) - - self._room_create = WidgetButton( - x=0.1, y=0.5, width=0.3, height=0.1, - label_text="Créer une salle", - - normal_texture=normal_region, - hover_texture=hover_region, - click_texture=click_region, - - label_batch=self.label_batch, - background_batch=self.background_batch, - ) - self.add_widget(self._room_create) - - self._room_join = WidgetButton( - x=0.1, y=0.4, width=0.3, height=0.1, - label_text="Rejoindre une salle", - - normal_texture=normal_region, - hover_texture=hover_region, - click_texture=click_region, - - label_batch=self.label_batch, - background_batch=self.background_batch, - ) - self.add_widget(self._room_join) - - super().on_window_added(window) - - def on_draw(self, window: "Window"): - super().on_draw(window) - - self.background_batch.draw() - self.label_batch.draw() diff --git a/source/gui/scene/Scene.py b/source/gui/scene/Scene.py new file mode 100644 index 0000000..87ff02b --- /dev/null +++ b/source/gui/scene/Scene.py @@ -0,0 +1,82 @@ +from functools import lru_cache +from typing import TYPE_CHECKING, Callable, Type, Any + +if TYPE_CHECKING: + from source.gui.window import Window + from source.gui.widget import Widget + + +class Scene: + """ + A scene that can be attached to a window. + It allows to switch the whole comportment of the window in a simpler way. + + It can react to any "on_" event from the window. + """ + + def __init__(self, window: "Window", *args, **kwargs): + self.window = window + self._widgets: set["Widget"] = set() + + # Widget Managing + + def add_widget(self, widget_class: Type["Widget"], *widget_args, **widget_kwargs) -> "Widget": + """ + Add a widget to the scene. + :widget_class: the class of the widget to add. + :widget_args: args for the creation of the widget object. + :widget_kwargs: kwargs for the creation of the widget object. + :return: the new created widget. + """ + + widget: "Widget" = widget_class(self, *widget_args, **widget_kwargs) + self._widgets.add(widget) + return widget + + def remove_widget(self, widget: "Widget") -> None: + """ + Remove a widget from the scene. + :scene: the widget to remove. + """ + + self._widgets.remove(widget) + + def clear_widget(self) -> None: + """ + Clear the scene from all the widgets. + """ + + self._widgets.clear() + + # Event Handling + + @lru_cache + def _event_wrapper(self, item: str) -> Callable: + """ + Un wrapper permettant d'appeler l'événement de tous les widgets attachées. + :param item: nom de la fonction à appeler dans le widget. + :return: une fonction appelant l'événement original ainsi que ceux des scènes. + """ + + # Récupère la fonction originale. S'il n'y en a pas, renvoie une fonction sans effet. + try: + func = super().__getattribute__(item) + except AttributeError: + func = lambda *_, **__: "pass" # NOQA E731 + + def _func(*args, **kwargs) -> None: + func(*args, **kwargs) + for widget in self._widgets: + getattr(widget, item, lambda *_, **__: "pass")(*args, **kwargs) + + return _func + + def __getattribute__(self, item: str) -> Any: + """ + Fonction appelée dès que l'on essaye d'accéder à l'un des attributs de l'objet. + :param item: nom de l'attribut recherché + :return: l'attribut de l'objet correspondant. + """ + + # si l'attribut est un événement (commence par "on_"), alors renvoie le dans un wrapper + return self._event_wrapper(item) if item.startswith("on_") else super().__getattribute__(item) diff --git a/source/gui/scene/__init__.py b/source/gui/scene/__init__.py index 0eeaf37..8abb0fe 100644 --- a/source/gui/scene/__init__.py +++ b/source/gui/scene/__init__.py @@ -1 +1 @@ -from .MainMenuScene import MainMenuScene +from .Scene import Scene diff --git a/source/gui/scene/abc/AbstractScene.py b/source/gui/scene/abc/AbstractScene.py deleted file mode 100644 index cd9c18c..0000000 --- a/source/gui/scene/abc/AbstractScene.py +++ /dev/null @@ -1,33 +0,0 @@ -from abc import ABC -from typing import TYPE_CHECKING, Optional - -if TYPE_CHECKING: - from source.gui.window import Window - from source.gui.widget.abc import AbstractWidget - - -class AbstractScene(ABC): - """ - An abstract scene that can be attached to a window. - Can be used to create a menu, an overlay, ... - """ - - def __init__(self, window: "Window"): - self._widgets: list["AbstractWidget"] = [] # the lists of the widgets in the scene - self._window: "Window" = window # the window where the scene is attached - - # widget - - def add_widget(self, *widgets: "AbstractWidget", priority: int = 0) -> None: - for widget in widgets: - self._widgets.insert(priority, widget) - widget.on_scene_added(self) - - def remove_widget(self, *widgets: "AbstractWidget") -> None: - for widget in widgets: - widget.on_scene_removed(self) - self._widgets.remove(widget) - - def clear_widget(self) -> None: - self.remove_widget(*self._widgets) - diff --git a/source/gui/scene/abc/__init__.py b/source/gui/scene/abc/__init__.py deleted file mode 100644 index 67c0fcc..0000000 --- a/source/gui/scene/abc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .AbstractScene import AbstractScene diff --git a/source/gui/scene/debug/FPSScene.py b/source/gui/scene/debug/FPSScene.py deleted file mode 100644 index 2d5a432..0000000 --- a/source/gui/scene/debug/FPSScene.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -import pyglet.window - -from source.gui.scene.abc import AbstractScene - -if TYPE_CHECKING: - from source.gui.window import Window - - -class FPSScene(AbstractScene): - """ - A base scene that can be used as an overlay to display the FPS - """ - - def __init__(self): - super().__init__() - self._fps_display: Optional[pyglet.window.FPSDisplay] = None - - def on_window_added(self, window: "Window"): - self._fps_display = pyglet.window.FPSDisplay(window) - - def on_draw(self, window: "Window"): - self._fps_display.draw() diff --git a/source/gui/scene/debug/__init__.py b/source/gui/scene/debug/__init__.py deleted file mode 100644 index a8d0b54..0000000 --- a/source/gui/scene/debug/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .FPSScene import FPSScene diff --git a/source/gui/scene/test/TestButtonScene.py b/source/gui/scene/test/TestButtonScene.py deleted file mode 100644 index 59157fa..0000000 --- a/source/gui/scene/test/TestButtonScene.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import TYPE_CHECKING - -import pyglet - -from source.gui.scene.abc import AbstractScene -from source.gui.widget import WidgetButton - -if TYPE_CHECKING: - from source.gui.window import Window - - -class TestButtonScene(AbstractScene): - """ - A scene used to test the Button widget - """ - - def __init__(self): - super().__init__() - - self.button_atlas = None - self.background_batch = None - self.label_batch = None - - def on_window_added(self, window: "Window") -> None: - normal_texture = pyglet.image.load("./assets/image/button/test_button_normal.png") - hover_texture = pyglet.image.load("./assets/image/button/test_button_hover.png") - click_texture = pyglet.image.load("./assets/image/button/test_button_clicking.png") - - self.button_atlas = pyglet.image.atlas.TextureAtlas() - normal_region = self.button_atlas.add(normal_texture) - hover_region = self.button_atlas.add(hover_texture) - click_region = self.button_atlas.add(click_texture) - - self.background_batch = pyglet.graphics.Batch() - self.label_batch = pyglet.graphics.Batch() - - for x in range(10): - for y in range(10): - self.add_widget(WidgetButton( - x=x*0.1, y=y*0.1, width=0.1, height=0.1, - - normal_texture=normal_region, - hover_texture=hover_region, - click_texture=click_region, - - label_text=f"TEST TEST CENTERING {x}.{y}", - label_multiline=True, - - label_batch=self.label_batch, - background_batch=self.background_batch, - )) - - super().on_window_added(window) - - def on_draw(self, window: "Window"): - super().on_draw(window) - - self.background_batch.draw() - self.label_batch.draw() diff --git a/source/gui/scene/test/TestLabelScene.py b/source/gui/scene/test/TestLabelScene.py deleted file mode 100644 index 10009c5..0000000 --- a/source/gui/scene/test/TestLabelScene.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import TYPE_CHECKING - -import pyglet - -from source.gui.scene.abc import AbstractScene -from source.gui.widget import WidgetLabel - -if TYPE_CHECKING: - from source.gui.window import Window - - -class TestLabelScene(AbstractScene): - """ - A scene used to test the Label widget - """ - - def __init__(self): - super().__init__() - - self.label_batch = None - - def on_window_added(self, window: "Window") -> None: - self.label_batch = pyglet.graphics.Batch() - - for x in range(10): - for y in range(10): - self.add_widget(WidgetLabel( - x=x*0.1, y=y*0.1, width=0.1, height=0.1, - - text=f"{x}.{y}", - - label_batch=self.label_batch, - )) - - super().on_window_added(window) - - def on_draw(self, window: "Window"): - super().on_draw(window) - - self.label_batch.draw() diff --git a/source/gui/scene/test/__init__.py b/source/gui/scene/test/__init__.py deleted file mode 100644 index eb8343c..0000000 --- a/source/gui/scene/test/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .TestButtonScene import TestButtonScene -from .TestLabelScene import TestLabelScene - diff --git a/source/gui/widget/FPSDisplay.py b/source/gui/widget/FPSDisplay.py new file mode 100644 index 0000000..f952151 --- /dev/null +++ b/source/gui/widget/FPSDisplay.py @@ -0,0 +1,22 @@ +from source.gui.widget import Widget + +from typing import TYPE_CHECKING + +import pyglet + +if TYPE_CHECKING: + from source.gui.scene import Scene + + +class FPSDisplay(Widget): + """ + A widget that display the current FPS of the scene's window + """ + + def __init__(self, scene: "Scene", *args, **kwargs): + super().__init__(scene, *args, **kwargs) + + self.fps_display = pyglet.window.FPSDisplay(scene.window) + + def on_draw(self): + self.fps_display.draw() diff --git a/source/gui/widget/Text.py b/source/gui/widget/Text.py new file mode 100644 index 0000000..a22443d --- /dev/null +++ b/source/gui/widget/Text.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING + +from source.gui.widget import Widget + +import pyglet + +if TYPE_CHECKING: + from source.gui.scene import Scene + + +class Text(Widget): + """ + A widget that display a text + """ + + def __init__(self, scene: "Scene", *args, **kwargs): + super().__init__(scene, *args, **kwargs) + + self.label = pyglet.text.Label(*args, **kwargs) + + def on_draw(self): + self.label.draw() diff --git a/source/gui/widget/Widget.py b/source/gui/widget/Widget.py new file mode 100644 index 0000000..bab8e93 --- /dev/null +++ b/source/gui/widget/Widget.py @@ -0,0 +1,15 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from source.gui.scene import Scene + + +class Widget: + """ + A Widget that can be attached to a scene. + + It can react to any "on_" event from the scene. + """ + + def __init__(self, scene: "Scene", *args, **kwargs): + self.scene = scene diff --git a/source/gui/widget/WidgetButton.py b/source/gui/widget/WidgetButton.py deleted file mode 100644 index 681934a..0000000 --- a/source/gui/widget/WidgetButton.py +++ /dev/null @@ -1,168 +0,0 @@ -from typing import TYPE_CHECKING, Callable, Optional - -import pyglet.image - -from source.gui.widget.base import Sprite, Label -from source.gui.widget.abc import AbstractResizableWidget -from source.type import Percentage -from source.utils import in_bbox - -if TYPE_CHECKING: - from source.gui.window import Window - from source.gui.scene.abc import AbstractScene - - -class WidgetButton(AbstractResizableWidget): - def __init__(self, - - # position - x: int | Percentage, - y: int | Percentage, - width: int | Percentage, - height: int | Percentage, - - # background - normal_texture: pyglet.image.AbstractImage, - hover_texture: pyglet.image.AbstractImage = None, - click_texture: pyglet.image.AbstractImage = None, - - # label - label_text: str = "", - label_font_name: str = None, - label_font_size: int = None, - label_bold: bool = False, - label_italic: bool = False, - label_stretch: bool = False, - label_color: tuple[int, int, int, int] = (255, 255, 255, 255), - label_align: str = "center", - label_multiline: bool = False, - label_dpi: int = None, - label_rotation: int = 0, - - # callback function - on_press: Callable = None, - on_release: Callable = None, - - # batch - label_batch: pyglet.graphics.Batch = None, - background_batch: pyglet.graphics.Batch = None, - - # group - label_group: pyglet.graphics.Group = None, - background_group: pyglet.graphics.Group = None, - ): - - super().__init__(x, y, width, height) - - self._normal_texture = normal_texture - self._hover_texture = hover_texture - self._click_texture = click_texture - - self.on_press = on_press - self.on_release = on_release - - self._label: Optional[Label] = None - self._label_kwargs = { - "text": label_text, - "font_name": label_font_name, - "font_size": label_font_size, - "bold": label_bold, - "italic": label_italic, - "stretch": label_stretch, - "color": label_color, - "align": label_align, - "multiline": label_multiline, - "dpi": label_dpi, - "rotation": label_rotation, - - "batch": label_batch, - "group": label_group, - } - - self._background: Optional[Sprite] = None - self._background_kwargs = { - "batch": background_batch, - "group": background_group, - } - - self._hover = False - self._click = False - - def on_window_added(self, window: "Window", scene: "AbstractScene"): - super().on_window_added(window, scene) - - self._label = Label( - x=self.x + self.width / 2, y=self.y + self.height / 2, width=self.width, - anchor_x="center", anchor_y="center", - - **self._label_kwargs - ) - - self._background = Sprite( - self._normal_texture, - x=self.x, y=self.y, width=self.width, height=self.height, - - **self._background_kwargs - ) - - # button update - - @property - def hover(self) -> bool: - return self._hover - - @hover.setter - def hover(self, hover: bool) -> None: - self._hover = hover - self.update_sprite() - - @property - def click(self) -> bool: - return self._click - - @click.setter - def click(self, click: bool) -> None: - self._click = click - self.update_sprite() - - @property - def background_texture(self) -> pyglet.image.AbstractImage: - return ( - self._click_texture if self._click else - self._hover_texture if self._hover else - self._normal_texture - ) - - def update_sprite(self): - self._background.image = self.background_texture - - def update_size(self): - self._background.update_size(self.x, self.y, self.width, self.height) - self._label.update_size(self.x + self.width // 2, self.y + self.height // 2, self.width) - - # event - - def on_mouse_motion(self, window: "Window", scene: "AbstractScene", x: int, y: int, dx: int, dy: int): - self.hover = in_bbox((x, y), self.bbox) - - def on_mouse_press(self, window: "Window", scene: "AbstractScene", x: int, y: int, button: int, modifiers: int): - if not in_bbox((x, y), self.bbox): return - - self.click = True - - if self.on_press is not None: - self.on_press(self, window, scene, x, y, button, modifiers) - - def on_mouse_release(self, window: "Window", scene: "AbstractScene", x: int, y: int, button: int, modifiers: int): - old_click = self.click - self.click = False - - if not in_bbox((x, y), self.bbox): return - - # if this button was the one hovered when the click was pressed - if old_click and self.on_release is not None: - self.on_release(self, window, scene, x, y, button, modifiers) - - -# TODO: on_resize seem really slow -# TODO: make the percentage dynamic or non dynamic diff --git a/source/gui/widget/WidgetImage.py b/source/gui/widget/WidgetImage.py deleted file mode 100644 index bf1856e..0000000 --- a/source/gui/widget/WidgetImage.py +++ /dev/null @@ -1,5 +0,0 @@ -from source.gui.widget.abc import AbstractResizableWidget - - -class WidgetImage(AbstractResizableWidget): - ... diff --git a/source/gui/widget/WidgetInput.py b/source/gui/widget/WidgetInput.py deleted file mode 100644 index 2fe5f61..0000000 --- a/source/gui/widget/WidgetInput.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import TYPE_CHECKING - -import pyglet.text - -from source.gui.widget.abc import AbstractResizableWidget -from source.type import Percentage - -if TYPE_CHECKING: - from source.gui.window import Window - from source.gui.scene.abc import AbstractScene - - -class WidgetInput(AbstractResizableWidget): - def __init__(self, - - # position - x: int | Percentage, - y: int | Percentage, - width: int | Percentage, - height: int | Percentage, - - # background - texture: pyglet.image.AbstractImage, - - # label - label_text: str = "", - label_font_name: str = None, - label_font_size: int = None, - label_bold: bool = False, - label_italic: bool = False, - label_stretch: bool = False, - label_color: tuple[int, int, int, int] = (255, 255, 255, 255), - label_align: str = "center", - label_multiline: bool = False, - label_dpi: int = None, - label_rotation: int = 0, - - # batch - label_batch: pyglet.graphics.Batch = None, - background_batch: pyglet.graphics.Batch = None, - - # group - label_group: pyglet.graphics.Group = None, - background_group: pyglet.graphics.Group = None, - ): - - super().__init__(x, y, width, height) - - self._label = None - self._label_kwargs = { - "text": label_text, - "font_name": label_font_name, - "font_size": label_font_size, - "bold": label_bold, - "italic": label_italic, - "stretch": label_stretch, - "color": label_color, - "align": label_align, - "multiline": label_multiline, - "dpi": label_dpi, - "rotation": label_rotation, - - "batch": label_batch, - "group": label_group, - } - - self._background_kwargs = { - "img": texture, - "batch": background_batch, - "group": background_group, - } - - def on_window_added(self, window: "Window", scene: "AbstractScene"): - ... diff --git a/source/gui/widget/WidgetLabel.py b/source/gui/widget/WidgetLabel.py deleted file mode 100644 index 2a9c562..0000000 --- a/source/gui/widget/WidgetLabel.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -import pyglet.text - -from source.gui.widget.base import Sprite, Label -from source.gui.widget.abc import AbstractResizableWidget -from source.type import Percentage - -if TYPE_CHECKING: - from source.gui.window import Window - from source.gui.scene.abc import AbstractScene - - -class WidgetLabel(AbstractResizableWidget): - def __init__(self, - - # position - x: int | Percentage, - y: int | Percentage, - width: int | Percentage = None, - height: int | Percentage = None, - - # label - text: str = "", - font_name: str = None, - font_size: int = None, - bold: bool = False, - italic: bool = False, - stretch: bool = False, - color: tuple[int, int, int, int] = (255, 255, 255, 255), - align: str = "center", - multiline: bool = False, - dpi: int = None, - rotation: int = 0, - - # background - texture: pyglet.image.AbstractImage = None, - - # batch - label_batch: pyglet.graphics.Batch = None, - background_batch: pyglet.graphics.Batch = None, - - # group - label_group: pyglet.graphics.Group = None, - background_group: pyglet.graphics.Group = None - ): - - super().__init__(x, y, width, height) - - self._label: Optional[Label] = None - self._label_kwargs = { - "text": text, - "font_name": font_name, - "font_size": font_size, - "bold": bold, - "italic": italic, - "stretch": stretch, - "color": color, - "align": align, - "multiline": multiline, - "dpi": dpi, - "rotation": rotation, - - "batch": label_batch, - "group": label_group, - } - - if texture is not None and width is None or height is None: - raise ValueError("You need to set a width and a height to create a Label with a background !") - - self._background = None - self._background_kwargs = { - "img": texture, - "batch": background_batch, - "group": background_group, - } if texture is not None else None - - def on_window_added(self, window: "Window", scene: "AbstractScene"): - super().on_window_added(window, scene) - - self._label = Label( - x=self.x, y=self.y, width=self.width, height=self.height, - **self._label_kwargs - ) - - if self._background_kwargs is not None: - self._background = Sprite( - x=self.x, y=self.y, width=self.width, height=self.height, - **self._background_kwargs - ) - - def update_size(self): - self._label.update_size(self.x, self.y, self.width, self.height) - if self._background is not None: self._background.update_size(self.x, self.y, self.width, self.height) diff --git a/source/gui/widget/__init__.py b/source/gui/widget/__init__.py index d59f93c..6b82082 100644 --- a/source/gui/widget/__init__.py +++ b/source/gui/widget/__init__.py @@ -1,4 +1,3 @@ -from .WidgetLabel import WidgetLabel -from .WidgetInput import WidgetInput -from .WidgetButton import WidgetButton -from .WidgetImage import WidgetImage +from .Widget import Widget +from .Text import Text +from .FPSDisplay import FPSDisplay diff --git a/source/gui/widget/abc/AbstractBoxWidget.py b/source/gui/widget/abc/AbstractBoxWidget.py deleted file mode 100644 index 37f90cd..0000000 --- a/source/gui/widget/abc/AbstractBoxWidget.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import ABC - -from source.gui.widget.abc import AbstractWidget -from source.type import BBox - - -class AbstractBoxWidget(AbstractWidget, ABC): - def __init__(self, x: int, y: int, width: int, height: int): - self._x = x - self._y = y - self._width = width - self._height = height - - # getter and setter, allow subclass to react when one of these value is changed - - @property - def x(self): - return self._x - - @x.setter - def x(self, x: int) -> None: - self._x = x - - @property - def y(self): - return self._y - - @y.setter - def y(self, y: int) -> None: - self._y = y - - @property - def width(self): - return self._width - - @width.setter - def width(self, width: int) -> None: - self._width = width - - @property - def height(self): - return self._height - - @height.setter - def height(self, height: int) -> None: - self._height = height - - @property - def bbox(self) -> BBox: - return self.x, self.y, self.x + self.width, self.y + self.height diff --git a/source/gui/widget/abc/AbstractResizableWidget.py b/source/gui/widget/abc/AbstractResizableWidget.py deleted file mode 100644 index adbbec9..0000000 --- a/source/gui/widget/abc/AbstractResizableWidget.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import ABC -from typing import Optional, TYPE_CHECKING - -from source.gui.widget.abc import AbstractBoxWidget -from source.type import Percentage - - -if TYPE_CHECKING: - from source.gui.window import Window - from source.gui.scene.abc import AbstractScene - - -class AbstractResizableWidget(AbstractBoxWidget, ABC): - def __init__(self, x: int | Percentage, y: int | Percentage, width: int | Percentage, height: int | Percentage): - super().__init__(x, y, width, height) - - self._window_width: Optional[int] = None - self._window_height: Optional[int] = None - - # getter / setter - - def on_window_added(self, window: "Window", scene: "AbstractScene"): - self._window_width = window.width - self._window_height = window.height - - @property - def x(self) -> int: - return self._x * self._window_width if isinstance(self._x, Percentage) else self._x - - @property - def y(self) -> int: - return self._y * self._window_height if isinstance(self._y, Percentage) else self._y - - @property - def width(self) -> int: - return self._width * self._window_width if isinstance(self._width, Percentage) else self._width - - @property - def height(self) -> int: - return self._height * self._window_height if isinstance(self._height, Percentage) else self._height - - # event - - def update_size(self): pass - - def on_resize(self, window: "Window", scene: "AbstractScene", width: int, height: int): - self._window_width = width - self._window_height = height - - self.update_size() diff --git a/source/gui/widget/abc/AbstractWidget.py b/source/gui/widget/abc/AbstractWidget.py deleted file mode 100644 index 29fd6e9..0000000 --- a/source/gui/widget/abc/AbstractWidget.py +++ /dev/null @@ -1,51 +0,0 @@ -from abc import ABC -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from source.gui.scene.abc import AbstractScene - from source.gui.window import Window - - -class AbstractWidget(ABC): - """ - This class represent a widget that can be attached to a Scene. - - It can be used to create a button, a label, ... - """ - - # widget event - - def on_scene_added(self, scene: "AbstractScene"): pass - def on_scene_removed(self, scene: "AbstractScene"): pass - - # scene event - - def on_window_added(self, window: "Window", scene: "AbstractScene"): pass - def on_window_removed(self, window: "Window", scene: "AbstractScene"): pass - - # global event - - def on_draw(self, window: "Window", scene: "AbstractScene"): pass - def on_resize(self, window: "Window", scene: "AbstractScene", width: int, height: int): pass - def on_hide(self, window: "Window", scene: "AbstractScene"): pass - def on_show(self, window: "Window", scene: "AbstractScene"): pass - def on_close(self, window: "Window", scene: "AbstractScene"): pass - def on_expose(self, window: "Window", scene: "AbstractScene"): pass - def on_activate(self, window: "Window", scene: "AbstractScene"): pass - def on_deactivate(self, window: "Window", scene: "AbstractScene"): pass - def on_text(self, window: "Window", scene: "AbstractScene", char: str): pass - def on_move(self, window: "Window", scene: "AbstractScene", x: int, y: int): pass - def on_context_lost(self, window: "Window", scene: "AbstractScene"): pass - def on_context_state_lost(self, window: "Window", scene: "AbstractScene"): pass - def on_key_press(self, window: "Window", scene: "AbstractScene", symbol: int, modifiers: int): pass - def on_key_release(self, window: "Window", scene: "AbstractScene", symbol: int, modifiers: int): pass - def on_key_held(self, window: "Window", scene: "AbstractScene", dt: float, symbol: int, modifiers: int): pass - def on_mouse_enter(self, window: "Window", scene: "AbstractScene", x: int, y: int): pass - def on_mouse_leave(self, window: "Window", scene: "AbstractScene", x: int, y: int): pass - def on_text_motion(self, window: "Window", scene: "AbstractScene", motion: int): pass - def on_text_motion_select(self, window: "Window", scene: "AbstractScene", motion: int): pass - def on_mouse_motion(self, window: "Window", scene: "AbstractScene", x: int, y: int, dx: int, dy: int): pass - def on_mouse_press(self, window: "Window", scene: "AbstractScene", x: int, y: int, button: int, modifiers: int): pass - def on_mouse_release(self, window: "Window", scene: "AbstractScene", x: int, y: int, button: int, modifiers: int): pass - def on_mouse_drag(self, window: "Window", scene: "AbstractScene", x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int): pass - def on_mouse_scroll(self, window: "Window", scene: "AbstractScene", x: int, y: int, scroll_x: float, scroll_y: float): pass diff --git a/source/gui/widget/abc/__init__.py b/source/gui/widget/abc/__init__.py deleted file mode 100644 index 77304a3..0000000 --- a/source/gui/widget/abc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .AbstractWidget import AbstractWidget -from .AbstractBoxWidget import AbstractBoxWidget -from .AbstractResizableWidget import AbstractResizableWidget diff --git a/source/gui/widget/base/Label.py b/source/gui/widget/base/Label.py deleted file mode 100644 index 69b237e..0000000 --- a/source/gui/widget/base/Label.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Optional - -import pyglet.text - - -class Label(pyglet.text.Label): - """ - Similar to the pyglet Label, but allow to update the size from a function - """ - - def update_size(self, x: int, y: int, width: Optional[int] = None, height: Optional[int] = None): - self.x = x - self.y = y - self.width = width - self.height = height diff --git a/source/gui/widget/base/Sprite.py b/source/gui/widget/base/Sprite.py deleted file mode 100644 index c4a3ea0..0000000 --- a/source/gui/widget/base/Sprite.py +++ /dev/null @@ -1,35 +0,0 @@ -import pyglet.sprite - - -class Sprite(pyglet.sprite.Sprite): - """ - Similar to the pyglet Sprite, but allow to change the width and height directly - and update the size from a function - """ - - def __init__(self, *args, width: int = None, height: int = None, **kwargs): - super().__init__(*args, **kwargs) - - self._orig_width: int = self.width - self._orig_height: int = self.height - - if width is not None: self.width = width - if height is not None: self.height = height - - # property - - @pyglet.sprite.Sprite.width.setter - def width(self, width: int): - self.scale_x = width / self._orig_width - - @pyglet.sprite.Sprite.height.setter - def height(self, height: int): - self.scale_y = height / self._orig_height - - # other event - - def update_size(self, x: int, y: int, width: int, height: int): - self.x = x - self.y = y - self.width = width - self.height = height diff --git a/source/gui/widget/base/__init__.py b/source/gui/widget/base/__init__.py deleted file mode 100644 index 70f4050..0000000 --- a/source/gui/widget/base/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .Label import Label -from .Sprite import Sprite diff --git a/source/gui/window/Window.py b/source/gui/window/Window.py index 1a05fd0..ac7ebf9 100644 --- a/source/gui/window/Window.py +++ b/source/gui/window/Window.py @@ -1,92 +1,96 @@ -from typing import TYPE_CHECKING, Callable, Type +from functools import lru_cache +from typing import Type, Callable, TYPE_CHECKING, Any + +import pyglet -import pyglet.window if TYPE_CHECKING: - from source.gui.scene.abc import AbstractScene + from source.gui.scene import Scene class Window(pyglet.window.Window): # NOQA """ - Similaire à la fenêtre de base de pyglet, mais permet d'ajouter des scènes. + A window. Based on the pyglet window object. + Scene can be added to the window """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # scene - self._scenes: list["AbstractScene"] = [] + self._scenes: set["Scene"] = set() - # a dictionary linking a key pressed to the corresponding event function - self._on_key_held_events: dict[tuple[int, int], Callable] = {} + # Scene Managing - # keys event handler - self.keys_handler = pyglet.window.key.KeyStateHandler() - self.push_handlers(self.keys_handler) - - # scene system - - def set_scene(self, scenes_type: Type["AbstractScene"]) -> "AbstractScene": + def set_scene(self, scene_class: Type["Scene"], *scene_args, **scene_kwargs) -> "Scene": """ - Clear all the previous scene and add a scene to the window - :param scenes_type: the class of the scene to add - :return: the 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. """ - self.clear() - return self.add_scene(scenes_type) - def add_scene(self, scene_type: Type["AbstractScene"], priority: int = 0) -> "AbstractScene": + self.clear_scene() + return self.add_scene(scene_class, *scene_args, **scene_kwargs) + + def add_scene(self, scene_class: Type["Scene"], *scene_args, **scene_kwargs) -> "Scene": """ - Add a scene to the window - :param scene_type: the class of the scene to add - :param priority: the priority of the scene in the display - :return: the scene + Add a 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. """ - scene: "AbstractScene" = scene_type(self) - self._scenes.insert(priority, scene) + + scene: "Scene" = scene_class(window=self, *scene_args, **scene_kwargs) + self._scenes.add(scene) return scene - def remove_scene(self, *scenes: "AbstractScene") -> None: + def remove_scene(self, scene: "Scene") -> None: """ - Remove scenes from the window - :param scenes: the scenes to remove + Remove a scene from the window. + :scene: the scene to remove. """ - for scene in scenes: - self._scenes.remove(scene) + + self._scenes.remove(scene) def clear_scene(self) -> None: """ - Remove all the scenes from the window + Clear the window from all the scenes. """ - self.remove_scene(*self._scenes) - # event + self._scenes.clear() - def scene_event_wrapper(self, name: str, func: Callable) -> Callable: + # Base Event + + def on_draw(self): # NOQA + self.clear() + + # Event Handling + + @lru_cache + def _event_wrapper(self, item: str) -> Callable: """ Un wrapper permettant d'appeler l'événement de toutes les scènes attachées. :param name: nom de la fonction à appeler dans la scène. - :param func: fonction originale à entourer du wrapper :return: une fonction appelant l'événement original ainsi que ceux des scènes. """ - def _func(*args, **kwargs): + func = super().__getattribute__(item) + + def _func(*args, **kwargs) -> None: func(*args, **kwargs) - for scene in self._scenes: getattr(scene, name, lambda *_, **__: "pass")(*args, **kwargs) + for scene in self._scenes: + getattr(scene, item)(*args, **kwargs) return _func - def __getattribute__(self, item: str): + def __getattribute__(self, item: str) -> Any: """ Fonction appelée dès que l'on essaye d'accéder à l'un des attributs de l'objet. :param item: nom de l'attribut recherché :return: l'attribut de l'objet correspondant. """ - # print(".", end="") - - attribute = super().__getattribute__(item) - - # si l'attribut est un évenement (commence par "on_"), alors renvoie le dans un wrapper - return self.scene_event_wrapper(item, attribute) if item.startswith("on_") else attribute - + # si l'attribut est un événement (commence par "on_"), alors renvoie le dans un wrapper + return self._event_wrapper(item) if item.startswith("on_") else super().__getattribute__(item)