implemented a Scene system for pyglet to simplify the creation of multiple different interface

This commit is contained in:
Faraphel 2023-01-05 08:53:25 +01:00
parent 445ade8c33
commit 69cf45bd72
8 changed files with 251 additions and 31 deletions

0
gui/__init__.py Normal file
View file

View file

@ -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()

View file

@ -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

43
gui/scene/Scene.py Normal file
View file

@ -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

3
gui/scene/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from .Scene import Scene
from .HelloWorldScene import HelloWorldScene
from .FPSCounterScene import FPSCounterScene

137
gui/window/Window.py Normal file
View file

@ -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)

1
gui/window/__init__.py Normal file
View file

@ -0,0 +1 @@
from .Window import Window

View file

@ -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()