rewrote the base of the Window, Scene and Widget class to make them as clean as possible
This commit is contained in:
parent
3cb8a94c13
commit
432a311b76
31 changed files with 228 additions and 843 deletions
14
NOTE.md
Normal file
14
NOTE.md
Normal file
|
@ -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 ?
|
23
main.pyw
23
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)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pyglet
|
||||
numpy
|
||||
pyglet==2.0.4
|
||||
numpy==1.24.2
|
||||
|
|
|
@ -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.
|
|
@ -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()
|
82
source/gui/scene/Scene.py
Normal file
82
source/gui/scene/Scene.py
Normal file
|
@ -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)
|
|
@ -1 +1 @@
|
|||
from .MainMenuScene import MainMenuScene
|
||||
from .Scene import Scene
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -1 +0,0 @@
|
|||
from .AbstractScene import AbstractScene
|
|
@ -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()
|
|
@ -1 +0,0 @@
|
|||
from .FPSScene import FPSScene
|
|
@ -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()
|
|
@ -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()
|
|
@ -1,3 +0,0 @@
|
|||
from .TestButtonScene import TestButtonScene
|
||||
from .TestLabelScene import TestLabelScene
|
||||
|
22
source/gui/widget/FPSDisplay.py
Normal file
22
source/gui/widget/FPSDisplay.py
Normal file
|
@ -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()
|
22
source/gui/widget/Text.py
Normal file
22
source/gui/widget/Text.py
Normal file
|
@ -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()
|
15
source/gui/widget/Widget.py
Normal file
15
source/gui/widget/Widget.py
Normal file
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
from source.gui.widget.abc import AbstractResizableWidget
|
||||
|
||||
|
||||
class WidgetImage(AbstractResizableWidget):
|
||||
...
|
|
@ -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"):
|
||||
...
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
from .AbstractWidget import AbstractWidget
|
||||
from .AbstractBoxWidget import AbstractBoxWidget
|
||||
from .AbstractResizableWidget import AbstractResizableWidget
|
|
@ -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
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||
from .Label import Label
|
||||
from .Sprite import Sprite
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue