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 import pyglet
from source.gui.scene.abc import Scene 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 from source.gui.window import Window
# Test Scene # Test Scene
@ -11,12 +11,47 @@ class TestScene(Scene):
def __init__(self, window: "Window"): def __init__(self, window: "Window"):
super().__init__(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) 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_pressed = lambda button, modifiers: print("pressed", label, button, modifiers)
label.on_release = lambda button, modifiers: print("release", 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 # Create a new window
window = Window(resizable=True, vsync=False) 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) 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() self.fps_display.draw()

View file

@ -34,5 +34,9 @@ class Text(BoxWidget):
self.label.width = self.width self.label.width = self.width
self.label.height = self.height 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() self.label.draw()

View file

@ -1,2 +1,3 @@
from .Text import Text from .Text import Text
from .FPSDisplay import FPSDisplay 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._hovering = False # is the button currently hovered ?
self._clicking = False # is the button currently clicked ? self._clicking = False # is the button currently clicked ?
# property
@property @property
def x(self) -> int: def x(self) -> int:
return self.scene.window.width * self._p_x return self.scene.window.width * self._p_x
@ -74,6 +76,24 @@ class BoxWidget(Widget, ABC):
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.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 # 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): # NOQA
@ -86,11 +106,11 @@ class BoxWidget(Widget, ABC):
:dy: the difference of the y mouse axis :dy: the difference of the y mouse axis
""" """
old_hovering = self._hovering old_hovering = self.hovering
self._hovering = in_bbox((x, y), self.bbox) self.hovering = in_bbox((x, y), self.bbox)
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() # call the hover enter event if self.hovering: self.on_hover_enter() # call the hover enter event
else: self.on_hover_leave() # call the hover leave event else: self.on_hover_leave() # call the hover leave event
def on_hover_enter(self): 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 this button was the one hovered when the click was pressed
if not in_bbox((x, y), self.bbox): return if not in_bbox((x, y), self.bbox): return
self._clicking = True self.clicking = True
self.on_press(button, modifiers) self.on_press(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 old_click: bool = self._clicking
self._clicking = False self.clicking = False
if not in_bbox((x, y), self.bbox): return if not in_bbox((x, y), self.bbox): return

View file

@ -1,2 +1,3 @@
from .copy_array_offset import copy_array_offset from .copy_array_offset import copy_array_offset
from .in_bbox import in_bbox 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()
}