added Button widget (label to fix)

This commit is contained in:
Faraphel 2023-02-13 20:32:23 +01:00
parent 59ee20efe5
commit 31d1069709
10 changed files with 235 additions and 10 deletions

View file

@ -1,7 +1,7 @@
import pyglet
from source.gui.scene.abc import Scene
from source.gui.widget import Text, FPSDisplay
from source.gui.widget import Text, FPSDisplay, Button
from source.gui.window import Window
# Test Scene
@ -11,12 +11,47 @@ class TestScene(Scene):
def __init__(self, window: "Window"):
super().__init__(window)
# loading resource
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")
button_atlas = pyglet.image.atlas.TextureAtlas()
normal_region = button_atlas.add(normal_texture)
hover_region = button_atlas.add(hover_texture)
click_region = button_atlas.add(click_texture)
self.background_batch = pyglet.graphics.Batch()
self.label_batch = pyglet.graphics.Batch()
# the widgets
self.add_widget(FPSDisplay)
label = self.add_widget(Text, text="Hello World !", x=0.5, y=0.5, width=0.5, height=0.5, anchor_x="center", anchor_y="center")
label = self.add_widget(
Button,
x=0.5, y=0.5, width=0.5, height=0.5,
texture_normal=normal_region,
texture_hover=hover_region,
texture_click=click_region,
label_text="Hello World !",
background_batch=self.background_batch,
label_batch=self.label_batch,
)
label.on_pressed = lambda button, modifiers: print("pressed", label, button, modifiers)
label.on_release = lambda button, modifiers: print("release", label, button, modifiers)
def on_draw(self):
self.background_batch.draw()
self.label_batch.draw()
# Create a new window
window = Window(resizable=True, vsync=False)

View file

@ -0,0 +1,24 @@
import pyglet
class Sprite(pyglet.sprite.Sprite):
"""
Same as the pyglet sprite, but allow to set a width and height easier
"""
def __init__(self, width: int = None, height: int = None, *args, **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
@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

View file

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

102
source/gui/widget/Button.py Normal file
View file

@ -0,0 +1,102 @@
from typing import TYPE_CHECKING, Optional
import pyglet
from source.gui.sprite import Sprite
from source.gui.widget.abc import BoxWidget
from source.type import Percentage
from source.utils import dict_prefix
if TYPE_CHECKING:
from source.gui.scene.abc import Scene
class Button(BoxWidget):
"""
A button widget with a background texture that change depending on if it is clicked or hovered, and a label.
You can pass parameter to the background and label by adding "background_" and "label_" before the parameter.
"""
def __init__(self, scene: "Scene",
texture_normal: pyglet.image.AbstractImage,
x: Percentage = 0,
y: Percentage = 0,
width: Percentage = None,
height: Percentage = None,
texture_hover: pyglet.image.AbstractImage = None,
texture_click: pyglet.image.AbstractImage = None,
**kwargs):
super().__init__(scene, x, y, width, height)
self._texture_normal: pyglet.image.AbstractImage = texture_normal
self._texture_hover: Optional[pyglet.image.AbstractImage] = texture_hover
self._texture_click: Optional[pyglet.image.AbstractImage] = texture_click
self.background = Sprite(
img=self._texture_normal,
x=self.x, y=self.y, width=self.width, height=self.height,
**dict_prefix("background_", kwargs)
)
self.label = pyglet.text.Label(
x=self.x, y=self.y, width=self.width, height=self.height,
**dict_prefix("label_", kwargs)
)
# background
@property
def background_texture(self) -> pyglet.image.AbstractImage:
"""
Return the correct texture for the background.
The clicking texture per default, if hover the hovered texture (if it exists)
and if click the clicking texture (if it exists)
:return: the corresponding texture
"""
return (
self._texture_click if self.clicking and self._texture_click is not None else
self._texture_hover if self.hovering and self._texture_hover is not None else
self._texture_normal
)
def _refresh_background(self) -> None:
self.background.image = self.background_texture
@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
def on_resize(self, width: int, height: int):
self.background.x = self.x
self.background.y = self.y
self.background.width = self.width
self.background.height = self.height
self.label.x = self.x
self.label.y = self.y
self.label.width = self.width
self.label.height = self.height
def draw(self):
"""
The draw function. Can be called to draw the widget, but can be ignored to draw it with batchs.
"""
self.background.draw()
self.label.draw()

View file

@ -18,5 +18,9 @@ class FPSDisplay(Widget):
self.fps_display = pyglet.window.FPSDisplay(scene.window)
def on_draw(self):
def draw(self):
"""
The draw function. Can be called to draw the widget, but can be ignored to draw it with batchs.
"""
self.fps_display.draw()

View file

@ -34,5 +34,9 @@ class Text(BoxWidget):
self.label.width = self.width
self.label.height = self.height
def on_draw(self):
def draw(self):
"""
The draw function. Can be called to draw the widget, but can be ignored to draw it with batchs.
"""
self.label.draw()

View file

@ -1,2 +1,3 @@
from .Text import Text
from .FPSDisplay import FPSDisplay
from .Button import Button

View file

@ -30,6 +30,8 @@ class BoxWidget(Widget, ABC):
self._hovering = False # is the button currently hovered ?
self._clicking = False # is the button currently clicked ?
# property
@property
def x(self) -> int:
return self.scene.window.width * self._p_x
@ -74,6 +76,24 @@ class BoxWidget(Widget, ABC):
def bbox(self) -> tuple[int, int, int, int]:
return self.x, self.y, self.x + self.width, self.y + self.height
# property that can be used to add event when these value are modified in some specific widget.
@property
def hovering(self):
return self._hovering
@hovering.setter
def hovering(self, hovering: bool):
self._hovering = hovering
@property
def clicking(self):
return self._clicking
@clicking.setter
def clicking(self, clicking: bool):
self._clicking = clicking
# event
def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): # NOQA
@ -86,11 +106,11 @@ class BoxWidget(Widget, ABC):
:dy: the difference of the y mouse axis
"""
old_hovering = self._hovering
self._hovering = in_bbox((x, y), self.bbox)
old_hovering = self.hovering
self.hovering = in_bbox((x, y), self.bbox)
if old_hovering != self._hovering: # if the hover changed
if self._hovering: self.on_hover_enter() # call the hover enter event
if old_hovering != self.hovering: # if the hover changed
if self.hovering: self.on_hover_enter() # call the hover enter event
else: self.on_hover_leave() # call the hover leave event
def on_hover_enter(self):
@ -107,13 +127,13 @@ class BoxWidget(Widget, ABC):
# if this button was the one hovered when the click was pressed
if not in_bbox((x, y), self.bbox): return
self._clicking = True
self.clicking = True
self.on_press(button, modifiers)
def on_mouse_release(self, x: int, y: int, button: int, modifiers: int):
old_click: bool = self._clicking
self._clicking = False
self.clicking = False
if not in_bbox((x, y), self.bbox): return

View file

@ -1,2 +1,3 @@
from .copy_array_offset import copy_array_offset
from .in_bbox import in_bbox
from .dict import dict_filter, dict_prefix

33
source/utils/dict.py Normal file
View file

@ -0,0 +1,33 @@
from typing import Callable, Any
def dict_filter(filter_func: Callable[[Any, Any], bool], dictionary: dict) -> dict:
"""
Filter a dict object with the filter function given.
:filter_func: the function to filter with
:dictionary: the dictionary to filter
:return: the filtered dictionary
"""
return {
k: v for k, v in filter(
lambda d: filter_func(d[0], d[1]),
dictionary.items()
)
}
def dict_prefix(prefix: str, dictionary: dict) -> dict:
"""
Take only the keys that start with the prefix, and remove this prefix from the keys.
:prefix: the prefix to use
:dictionary: the dictionary to filter
:return: the dictionary with the prefix
"""
return {
k.removeprefix(prefix): v for k, v in dict_filter(
lambda k, v: k.startswith(prefix),
dictionary
).items()
}