simplified event in Widget with Listener, added more useful property to BoxWidget

This commit is contained in:
Faraphel 2023-02-18 19:41:07 +01:00
parent ca59abee01
commit 446e377b19
13 changed files with 118 additions and 124 deletions

17
source/event/Listener.py Normal file
View file

@ -0,0 +1,17 @@
from typing import Callable
class Listener:
def __init__(self):
self._events_listener: dict[str, set[Callable]] = {}
def add_listener(self, name: str, callback: Callable):
if name not in self._events_listener: self._events_listener[name] = set()
self._events_listener[name].add(callback)
def remove_listener(self, name: str, callback: Callable):
self._events_listener[name].remove(callback)
def trigger_event(self, name: str, *args, **kwargs):
for listener in self._events_listener.get(name, set()):
listener(*args, **kwargs)

1
source/event/__init__.py Normal file
View file

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

View file

@ -45,7 +45,7 @@ class MainMenu(Scene):
texture_click=texture_button_click texture_click=texture_button_click
) )
self.game_create.on_release = lambda *_: self.window.set_scene(scene.RoomCreate) self.game_create.add_listener("on_click_release", lambda *_: self.window.set_scene(scene.RoomCreate))
self.game_join = self.add_widget( self.game_join = self.add_widget(
widget.Button, widget.Button,
@ -60,7 +60,7 @@ class MainMenu(Scene):
texture_click=texture_button_click texture_click=texture_button_click
) )
self.game_join.on_release = lambda *_: self.window.set_scene(scene.RoomJoin) self.game_join.add_listener("on_click_release", lambda *_: self.window.set_scene(scene.RoomJoin))
self.settings = self.add_widget( self.settings = self.add_widget(
widget.Button, widget.Button,
@ -75,7 +75,7 @@ class MainMenu(Scene):
texture_click=texture_button_click texture_click=texture_button_click
) )
self.settings.on_release = lambda *_: self.window.set_scene(scene.Settings) self.settings.add_listener("on_click_release", lambda *_: self.window.set_scene(scene.Settings))
self.fps_display = self.add_widget(FPSDisplay, color=(255, 255, 255, 180)) self.fps_display = self.add_widget(FPSDisplay, color=(255, 255, 255, 180))

View file

@ -36,7 +36,7 @@ class RoomCreate(Scene):
) )
from source.gui.scene import MainMenu from source.gui.scene import MainMenu
self.back.on_release = lambda *_: self.window.set_scene(MainMenu) self.back.add_listener("on_click_release", lambda *_: self.window.set_scene(MainMenu))
self.label_ip = self.add_widget( self.label_ip = self.add_widget(
widget.Text, widget.Text,

View file

@ -34,7 +34,7 @@ class RoomJoin(Scene):
) )
from source.gui.scene import MainMenu from source.gui.scene import MainMenu
self.back.on_release = lambda *_: self.window.set_scene(MainMenu) self.back.add_listener("on_click_release", lambda *_: self.window.set_scene(MainMenu))
self.entry_ip = self.add_widget( self.entry_ip = self.add_widget(
widget.Input, widget.Input,
@ -69,12 +69,12 @@ class RoomJoin(Scene):
texture_click=texture_button_click texture_click=texture_button_click
) )
self.connect.on_release = lambda *_: network.Client( self.connect.add_listener("on_click_release", lambda *_: network.Client(
window=self.window, window=self.window,
ip_address=self.entry_ip.text, ip_address=self.entry_ip.text,
daemon=True, daemon=True,
username="Client" username="Client"
).start() ).start())
def on_draw(self): def on_draw(self):
self.back.draw() self.back.draw()

View file

@ -37,7 +37,7 @@ class Settings(Scene):
) )
from source.gui.scene import MainMenu from source.gui.scene import MainMenu
self.back.on_release = lambda *_: self.window.set_scene(MainMenu) self.back.add_listener("on_click_release", lambda *_: self.window.set_scene(MainMenu))
self.checkbox = self.add_widget( self.checkbox = self.add_widget(
widget.Checkbox, widget.Checkbox,

View file

@ -47,6 +47,9 @@ class Button(BoxWidget):
**dict_prefix("label_", kwargs) **dict_prefix("label_", kwargs)
) )
self.add_listener("on_hover_change", lambda *_: self._refresh_background())
self.add_listener("on_click_change", lambda *_: self._refresh_background())
self._refresh_size() # refresh the size and position for the background and label self._refresh_size() # refresh the size and position for the background and label
# background # background
@ -76,20 +79,7 @@ class Button(BoxWidget):
self.background.width, self.background.height = self.size self.background.width, self.background.height = self.size
# center the label # center the label
self.label.x = self.x + (self.width / 2) self.label.x, self.label.y = self.center
self.label.y = self.y + (self.height / 2)
@BoxWidget.hovering.setter
def hovering(self, hovering: bool):
# when the hover state is changed, update the background
BoxWidget.hovering.fset(self, hovering)
self._refresh_background()
@BoxWidget.clicking.setter
def clicking(self, clicking: bool):
# when the clicking state is changed, update the background
BoxWidget.clicking.fset(self, clicking)
self._refresh_background()
# event # event

View file

@ -34,6 +34,8 @@ class Checkbox(BoxWidget):
self.state = state self.state = state
self.add_listener("on_click_release", lambda *_: self.swap_state())
self._refresh_size() self._refresh_size()
# refreshing # refreshing
@ -60,14 +62,13 @@ class Checkbox(BoxWidget):
self._state = state self._state = state
self._refresh_tick() self._refresh_tick()
def swap_state(self):
self.state = not self.state # inverse l'état
# event # event
def on_resize(self, width: int, height: int): def on_resize(self, width: int, height: int):
self._refresh_size() self._refresh_size()
def on_release(self, rel_x: int, rel_y: int, button: int, modifiers: int):
# lorsque le bouton est enclenché, inverse son état
self.state = not self.state
def draw(self): def draw(self):
self.tick.draw() self.tick.draw()

View file

@ -49,6 +49,11 @@ class GameGrid(BoxWidget):
**dict_prefix("cursor_", kwargs) **dict_prefix("cursor_", kwargs)
) )
self.add_listener("on_hover_leave", lambda *_: self.hide_cursor())
self.add_listener("on_hover", self._refresh_cursor)
self.add_listener("on_click_release",
lambda rel_x, rel_y, *_: print("click", self.get_cell_from_rel(rel_x, rel_y)))
self._refresh_size() self._refresh_size()
def get_cell_from_rel(self, rel_x: int, rel_y: int) -> tuple[int, int]: def get_cell_from_rel(self, rel_x: int, rel_y: int) -> tuple[int, int]:
@ -68,14 +73,24 @@ class GameGrid(BoxWidget):
line.x = self.x + self.cell_width * column line.x = self.x + self.cell_width * column
line.x2 = line.x line.x2 = line.x
line.y = self.y line.y = self.y
line.y2 = self.y + self.height line.y2 = self.y2
for row, line in enumerate(self.lines[-self._rows+1:], start=1): for row, line in enumerate(self.lines[-self._rows+1:], start=1):
line.x = self.x line.x = self.x
line.x2 = self.x + self.width line.x2 = self.x2
line.y = self.y + self.cell_height * row line.y = self.y + self.cell_height * row
line.y2 = line.y line.y2 = line.y
def _refresh_cursor(self, rel_x: int, rel_y: int):
cell_x, cell_y = self.get_cell_from_rel(rel_x, rel_y)
self.cursor.x = self.x + cell_x * self.width / self._columns
self.cursor.y = self.y + cell_y * self.height / self._rows
self.cursor.width, self.cursor.height = self.cell_size
def hide_cursor(self):
self.cursor.width, self.cursor.height = 0, 0
# property # property
@property @property
@ -92,19 +107,6 @@ class GameGrid(BoxWidget):
# event # event
def on_hover(self, rel_x: int, rel_y: int):
cell_x, cell_y = self.get_cell_from_rel(rel_x, rel_y)
self.cursor.x = self.x + cell_x * self.width / self._columns
self.cursor.y = self.y + cell_y * self.height / self._rows
self.cursor.width, self.cursor.height = self.cell_size
def on_hover_leave(self, rel_x: int, rel_y: int):
self.cursor.width, self.cursor.height = 0, 0
def on_release(self, rel_x: int, rel_y: int, button: int, modifiers: int):
print("click", (rel_x, rel_y), self.get_cell_from_rel(rel_x, rel_y))
def on_resize(self, width: int, height: int): def on_resize(self, width: int, height: int):
self._refresh_size() self._refresh_size()

View file

@ -47,6 +47,8 @@ class Input(BoxWidget):
**dict_prefix("label_", kwargs) **dict_prefix("label_", kwargs)
) )
self.add_listener("on_activate_change", lambda *_: self._refresh_background())
self._refresh_size() self._refresh_size()
# background # background
@ -76,13 +78,7 @@ class Input(BoxWidget):
self.background.width, self.background.height = self.size self.background.width, self.background.height = self.size
# center the label # center the label
self.label.x = self.x + (self.width / 2) self.label.x, self.label.y = self.center
self.label.y = self.y + (self.height / 2)
@BoxWidget.activated.setter
def activated(self, activated: bool) -> None:
BoxWidget.activated.fset(self, activated)
self._refresh_background()
# property # property

View file

@ -48,6 +48,8 @@ class Scroller(BoxWidget):
**dict_prefix("label_", kwargs) **dict_prefix("label_", kwargs)
) )
self.add_listener("on_click_release", lambda rel_x, *_: self._refresh_cursor(rel_x))
self._from = from_ self._from = from_
self._to = to self._to = to
self.value = value self.value = value
@ -73,15 +75,14 @@ class Scroller(BoxWidget):
) )
# label # label
self.label.x = self.x + (self.width / 2) self.label.x, self.label.y = self.center
self.label.y = self.y + (self.height / 2)
self.label.text = str(self.text_transform(self.value)) self.label.text = str(self.text_transform(self.value))
# property def _refresh_cursor(self, rel_x: int):
def on_pressed(self, rel_x: int, rel_y: int, button: int, modifiers: int):
self.value = rel_x / self.width self.value = rel_x / self.width
# property
@property @property
def value(self): def value(self):
return self._value return self._value

View file

@ -27,9 +27,9 @@ class BoxWidget(Widget, ABC):
self.width = width self.width = width
self.height = height self.height = height
self._hovering = False # is the button currently hovered ? self.hovering = False # is the button currently hovered ?
self._clicking = False # is the button currently clicked ? self.clicking = False # is the button currently clicked ?
self._activated = False # is the button activated ? (the last click was inside this widget) self.activated = False # is the button activated ? (the last click was inside this widget)
# property # property
@ -64,6 +64,22 @@ class BoxWidget(Widget, ABC):
def y(self, y: Distance): def y(self, y: Distance):
self._y = y self._y = y
@property
def xy(self) -> tuple[int, int]:
return self.x, self.y
@property
def x2(self) -> int:
return self.x + self.width
@property
def y2(self) -> int:
return self.y + self.height
@property
def xy2(self) -> tuple[int, int]:
return self.x2, self.y2
@property @property
def width(self) -> int: def width(self) -> int:
return self._getter_distance(self.scene.window.width, self._width) return self._getter_distance(self.scene.window.width, self._width)
@ -80,47 +96,29 @@ class BoxWidget(Widget, ABC):
def height(self, height: Optional[Distance]): def height(self, height: Optional[Distance]):
self._height = height self._height = height
@property
def xy(self) -> tuple[int, int]:
return self.x, self.y
@property @property
def size(self) -> tuple[int, int]: def size(self) -> tuple[int, int]:
return self.width, self.height return self.width, self.height
@property @property
def bbox(self) -> tuple[int, int, int, int]: def bbox(self) -> tuple[int, int, int, int]:
return self.x, self.y, self.x + self.width, self.y + self.height return self.x, self.y, self.x2, self.y2
# property that can be used to add event when these value are modified in some specific widget.
@property @property
def hovering(self): def center_x(self) -> float:
return self._hovering return self.x + (self.width / 2)
@hovering.setter
def hovering(self, hovering: bool):
self._hovering = hovering
@property @property
def clicking(self): def center_y(self) -> float:
return self._clicking return self.y + (self.height / 2)
@clicking.setter
def clicking(self, clicking: bool):
self._clicking = clicking
@property @property
def activated(self): def center(self) -> tuple[float, float]:
return self._activated return self.center_x, self.center_y
@activated.setter
def activated(self, activated: bool):
self._activated = activated
# event # event
def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): # NOQA def on_mouse_motion(self, x: int, y: int, dx: int, dy: int):
""" """
When the mouse is moved, this event is triggered. When the mouse is moved, this event is triggered.
Allow the implementation of the on_hover_enter and on_hover_leave events Allow the implementation of the on_hover_enter and on_hover_leave events
@ -130,60 +128,44 @@ class BoxWidget(Widget, ABC):
:dy: the difference of the y mouse axis :dy: the difference of the y mouse axis
""" """
rel_x, rel_y = x - self.x, y - self.y
old_hovering = self.hovering old_hovering = self.hovering
self.hovering = in_bbox((x, y), self.bbox) self.hovering = in_bbox((x, y), self.bbox)
rel_x, rel_y = x - self.x, y - self.y
if old_hovering != self.hovering: # if the hover changed if old_hovering != self.hovering: # if the hover changed
if self.hovering: self.on_hover_enter(rel_x, rel_y) # call the hover enter event # call the hover changed event
else: self.on_hover_leave(rel_x, rel_y) # call the hover leave event self.trigger_event("on_hover_change", rel_x, rel_y)
# call the hover enter / leave event
self.trigger_event("on_hover_enter" if self.hovering else "on_hover_leave", rel_x, rel_y)
if self.hovering: # if the mouse motion is inside the collision if self.hovering: # if the mouse motion is inside the collision
self.on_hover(rel_x, rel_y) # call the hover event self.trigger_event("on_hover", rel_x, rel_y) # call the hover event
def on_hover(self, rel_x: int, rel_y: int):
"""
This event is called when the mouse move in the bbox of the widget
"""
def on_hover_enter(self, rel_x: int, rel_y: int):
"""
This event is called when the mouse enter the bbox of the widget
"""
def on_hover_leave(self, rel_x: int, rel_y: int):
"""
This event is called when the mouse leave the bbox of the widget
"""
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int): def on_mouse_press(self, x: int, y: int, button: int, modifiers: int):
# if this button was the one hovered when the click was pressed rel_x, rel_y = x - self.x, y - self.y
if not in_bbox((x, y), self.bbox): self.activated = in_bbox((x, y), self.bbox)
self.activated = False # if the click was not in the bbox, disable the activated state self.trigger_event("on_activate_change", rel_x, rel_y, button, modifiers)
return
self.activated = True # if the click is inside the bbox, enable the activated state if self.activated: # if the click was inside the widget
self.clicking = True # the widget is now clicked self.trigger_event("on_activate_enter", rel_x, rel_y, button, modifiers)
self.on_pressed(x - self.x, y - self.y, button, modifiers) self.clicking = True # the widget is also now clicked
self.trigger_event("on_click_change", rel_x, rel_y, button, modifiers)
self.trigger_event("on_click_press", rel_x, rel_y, button, modifiers)
else:
self.trigger_event("on_activate_leave", rel_x, rel_y, button, modifiers)
def on_mouse_release(self, x: int, y: int, button: int, modifiers: int): def on_mouse_release(self, x: int, y: int, button: int, modifiers: int):
old_click: bool = self._clicking rel_x, rel_y = x - self.x, y - self.y
old_click: bool = self.clicking
self.clicking = False # the widget is no longer clicked self.clicking = False # the widget is no longer clicked
if not in_bbox((x, y), self.bbox): return if not in_bbox((x, y), self.bbox): return # if the release was not in the collision, ignore
# if this button was the one hovered when the click was pressed if old_click: # if this button was the one hovered when the click was pressed
if old_click: self.on_release(x - self.x, y - self.y, button, modifiers) self.trigger_event("on_click_change", rel_x, rel_y, button, modifiers)
self.trigger_event("on_click_release", rel_x, rel_y, button, modifiers)
def on_pressed(self, rel_x: int, rel_y: int, button: int, modifiers: int):
"""
This event is called when the bbox is pressed
"""
def on_release(self, rel_x: int, rel_y: int, button: int, modifiers: int):
"""
This event is called when the bbox is released
"""

View file

@ -1,11 +1,13 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from source.event import Listener
if TYPE_CHECKING: if TYPE_CHECKING:
from source.gui.scene.abc import Scene from source.gui.scene.abc import Scene
class Widget(ABC): class Widget(Listener, ABC):
""" """
A Widget that can be attached to a scene. A Widget that can be attached to a scene.
@ -13,6 +15,8 @@ class Widget(ABC):
""" """
def __init__(self, scene: "Scene", **kwargs): def __init__(self, scene: "Scene", **kwargs):
super().__init__()
self.scene = scene self.scene = scene
@abstractmethod @abstractmethod