From 69cf45bd72a833c9a9cd46eb6b14d1af771e3c1b Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 5 Jan 2023 08:53:25 +0100 Subject: [PATCH] implemented a Scene system for pyglet to simplify the creation of multiple different interface --- gui/__init__.py | 0 gui/scene/FPSCounterScene.py | 23 ++++++ gui/scene/HelloWorldScene.py | 38 ++++++++++ gui/scene/Scene.py | 43 +++++++++++ gui/scene/__init__.py | 3 + gui/window/Window.py | 137 +++++++++++++++++++++++++++++++++++ gui/window/__init__.py | 1 + main.pyw | 37 ++-------- 8 files changed, 251 insertions(+), 31 deletions(-) create mode 100644 gui/__init__.py create mode 100644 gui/scene/FPSCounterScene.py create mode 100644 gui/scene/HelloWorldScene.py create mode 100644 gui/scene/Scene.py create mode 100644 gui/scene/__init__.py create mode 100644 gui/window/Window.py create mode 100644 gui/window/__init__.py diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/scene/FPSCounterScene.py b/gui/scene/FPSCounterScene.py new file mode 100644 index 0000000..cb963bc --- /dev/null +++ b/gui/scene/FPSCounterScene.py @@ -0,0 +1,23 @@ +from typing import Optional + +import pyglet + +from gui.scene import Scene +from gui.window import Window + + +class FPSCounterScene(Scene): + """ + This scene represent a simple FPS Counter. + """ + + def __init__(self): + self.fps_display: Optional[pyglet.window.FPSDisplay] = None + + def on_window_added(self, window: Window): + # the fps display need to be defined here because it is the moment where the window is first accessible + self.fps_display = pyglet.window.FPSDisplay(window=window) + + def on_draw(self, window: Window) -> None: + self.fps_display.draw() + diff --git a/gui/scene/HelloWorldScene.py b/gui/scene/HelloWorldScene.py new file mode 100644 index 0000000..d9c4b49 --- /dev/null +++ b/gui/scene/HelloWorldScene.py @@ -0,0 +1,38 @@ +import pyglet + +from gui.scene import Scene +from gui.window import Window + + +class HelloWorldScene(Scene): + """ + This scene is a simple Hello World. + + You can type anything with the keyboard or use backspace to remove characters. + The text is centered on the screen. + """ + + def __init__(self): + self._backspace_hold_frame: int = 0 + + self.label = pyglet.text.Label( + "Hello World !", + anchor_x="center", + anchor_y="center" + ) + + def on_draw(self, window: Window) -> None: + if window.keys[pyglet.window.key.BACKSPACE]: + if self._backspace_hold_frame % 5 == 0: self.label.text = self.label.text[:-1] + self._backspace_hold_frame += 1 + else: + self._backspace_hold_frame = 0 + + self.label.draw() + + def on_resize(self, window: Window, width: int, height: int) -> None: + self.label.x = width // 2 + self.label.y = height // 2 + + def on_text(self, window: Window, char: str): + self.label.text += char diff --git a/gui/scene/Scene.py b/gui/scene/Scene.py new file mode 100644 index 0000000..8bdbaf0 --- /dev/null +++ b/gui/scene/Scene.py @@ -0,0 +1,43 @@ +import pyglet.event + +from gui.window import Window + + +class Scene(pyglet.event.EventDispatcher): + """ + This class represent a scene that can be applied to a pyglet window. + + The scene can represent anything like the main menu, the game, the + options' menu, the multiplayer menu, ... + """ + + def on_window_added(self, window: Window): pass # when the Scene is added to a window + def on_window_removed(self, window: Window): pass # when the Scene is removed from a window + + def on_draw(self, window: Window): pass + def on_resize(self, window: Window, width: int, height: int): pass + def on_hide(self, window: Window): pass + def on_show(self, window: Window): pass + def on_close(self, window: Window): pass + def on_expose(self, window: Window): pass + def on_activate(self, window: Window): pass + def on_deactivate(self, window: Window): pass + def on_text(self, window: Window, char: str): pass + def on_move(self, window: Window, x: int, y: int): pass + def on_context_lost(self, window: Window): pass + def on_context_state_lost(self, window: Window): pass + def on_key_press(self, window: Window, symbol: int, modifiers: int): pass + def on_key_release(self, window: Window, symbol: int, modifiers: int): pass + def on_mouse_enter(self, window: Window, x: int, y: int): pass + def on_mouse_leave(self, window: Window, x: int, y: int): pass + def on_text_motion(self, window: Window, motion: int): pass + def on_text_motion_select(self, window: Window, motion: int): pass + def on_mouse_motion(self, window: Window, x: int, y: int, dx: int, dy: int): pass + def on_mouse_press(self, window: Window, x: int, y: int, button: int, modifiers: int): pass + def on_mouse_release(self, window: Window, x: int, y: int, button: int, modifiers: int): pass + def on_mouse_drag(self, window: Window, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int): pass + def on_mouse_scroll(self, window: Window, x: int, y: int, scroll_x: float, scroll_y: float): pass + + + + diff --git a/gui/scene/__init__.py b/gui/scene/__init__.py new file mode 100644 index 0000000..5d2aaee --- /dev/null +++ b/gui/scene/__init__.py @@ -0,0 +1,3 @@ +from .Scene import Scene +from .HelloWorldScene import HelloWorldScene +from .FPSCounterScene import FPSCounterScene diff --git a/gui/window/Window.py b/gui/window/Window.py new file mode 100644 index 0000000..a57954d --- /dev/null +++ b/gui/window/Window.py @@ -0,0 +1,137 @@ +from typing import Optional + +import pyglet.window + +from gui.scene import Scene + + +class Window(pyglet.window.Window): # NOQA - pycharm think pyglet window is abstract + """ + This class represent a Window based on the pyglet Window object. + + Allow to use a "Scene" system to create very different interface like + a main menu, options menu, ... that can overlay each other without + putting everything in the window code. + """ + + def __init__(self, *args, scenes: Optional[Scene] = None, **kwargs): + super().__init__(*args, **kwargs) + self._scenes: list[Scene] = [] if scenes is None else scenes + + # add a keys handler to the window + self.keys = pyglet.window.key.KeyStateHandler() + self.push_handlers(self.keys) + + # scene methods + + def set_scene(self, scene: Scene) -> None: + """ + Set the scene of the window + :param scene: the scene to set + """ + self._scenes = [scene] + scene.on_window_added(self) + + def clear_scene(self) -> None: + """ + Clear all the scenes of the window + """ + for scene in self._scenes: scene.on_window_removed(self) + self._scenes.clear() + + def add_scene(self, scene: Scene, priority: int = 0) -> None: + """ + Add a scene to the window + :param scene: the scene to add + :param priority: the priority level of the scene. The higher, the more the scene will be drawn on top + """ + self._scenes.insert(priority, scene) + scene.on_window_added(self) + + def remove_scene(self, scene: Scene) -> None: + """ + Remove a scene from the window + :param scene: the scene to remove + """ + scene.on_window_removed(self) + self._scenes.remove(scene) + + # window event methods + + # NOTE: it is too difficult to refactor all the event because : + # - There is no event "on_any_event" or equivalent + # - The list of all the event is not available in the source + # - Some event need special code like on_draw with the clear, on_resize with the super, ... + + def on_draw(self): # NOQA + self.clear() # clear the window to reset it + for scene in self._scenes: scene.on_draw(self) + + def on_resize(self, width: int, height: int): + super().on_resize(width, height) # this function is already defined and used + for scene in self._scenes: scene.on_resize(self, width, height) + + def on_hide(self): + for scene in self._scenes: scene.on_hide(self) + + def on_show(self): + for scene in self._scenes: scene.on_show(self) + + def on_close(self): + super().close() # this function is already defined and used + for scene in self._scenes: scene.on_close(self) + + def on_expose(self): + for scene in self._scenes: scene.on_expose(self) + + def on_activate(self): + for scene in self._scenes: scene.on_activate(self) + + def on_deactivate(self): + for scene in self._scenes: scene.on_deactivate(self) + + def on_text(self, char: str): + for scene in self._scenes: scene.on_text(self, char) + + def on_move(self, x: int, y: int): + for scene in self._scenes: scene.on_move(self, x, y) + + def on_context_lost(self): + for scene in self._scenes: scene.on_context_lost(self) + + def on_context_state_lost(self): + for scene in self._scenes: scene.on_context_state_lost(self) + + def on_key_press(self, symbol: int, modifiers: int): + super().on_key_press(symbol, modifiers) # this function is already defined and used + for scene in self._scenes: scene.on_key_press(self, symbol, modifiers) + + def on_key_release(self, symbol: int, modifiers: int): + for scene in self._scenes: scene.on_key_release(self, symbol, modifiers) + + def on_mouse_enter(self, x: int, y: int): + for scene in self._scenes: scene.on_mouse_enter(self, x, y) + + def on_mouse_leave(self, x: int, y: int): + for scene in self._scenes: scene.on_mouse_leave(self, x, y) + + def on_text_motion(self, motion: int): + for scene in self._scenes: scene.on_text_motion(self, motion) + + def on_text_motion_select(self, motion: int): + for scene in self._scenes: scene.on_text_motion_select(self, motion) + + def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): + for scene in self._scenes: scene.on_mouse_motion(self, x, y, dx, dy) + + def on_mouse_press(self, x: int, y: int, button: int, modifiers: int): + for scene in self._scenes: scene.on_mouse_press(self, x, y, button, modifiers) + + def on_mouse_release(self, x: int, y: int, button: int, modifiers: int): + for scene in self._scenes: scene.on_mouse_release(self, x, y, button, modifiers) + + def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int): + for scene in self._scenes: scene.on_mouse_drag(self, x, y, dx, dy, buttons, modifiers) + + def on_mouse_scroll(self, x: int, y: int, scroll_x: float, scroll_y: float): + for scene in self._scenes: scene.on_mouse_scroll(self, x, y, scroll_x, scroll_y) diff --git a/gui/window/__init__.py b/gui/window/__init__.py new file mode 100644 index 0000000..262037f --- /dev/null +++ b/gui/window/__init__.py @@ -0,0 +1 @@ +from .Window import Window diff --git a/main.pyw b/main.pyw index 207fb08..40f3c09 100644 --- a/main.pyw +++ b/main.pyw @@ -1,39 +1,14 @@ import pyglet +from gui.window import Window +from gui.scene import HelloWorldScene, FPSCounterScene # Créer une fenêtre -window = pyglet.window.Window(resizable=True) - -# Créer un texte "Hello World !" -label = pyglet.text.Label( - "Hello World !", - anchor_x="center", - anchor_y="center" -) - - -# Lorsque la fenêtre change de taille, change la position du texte pour le centrer -@window.event -def on_resize(width: int, height: int): - label.x = width // 2 - label.y = height // 2 - - -# Lorsqu'une touche est enfoncée -@window.event -def on_key_press(symbol, modifiers): - print( - pyglet.window.key.symbol_string(symbol), - pyglet.window.key.modifiers_string(modifiers) - ) - - -# À chaque frame, rafraichi la fenêtre et dessine le texte -@window.event -def on_draw(): - window.clear() - label.draw() +window = Window(resizable=True, visible=False) +window.add_scene(HelloWorldScene()) +window.add_scene(FPSCounterScene()) # Lance la fenêtre +window.set_visible(True) pyglet.app.run()