added Button widget (label to fix)
This commit is contained in:
parent
59ee20efe5
commit
31d1069709
10 changed files with 235 additions and 10 deletions
39
main.pyw
39
main.pyw
|
@ -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)
|
||||||
|
|
24
source/gui/sprite/Sprite.py
Normal file
24
source/gui/sprite/Sprite.py
Normal 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
|
1
source/gui/sprite/__init__.py
Normal file
1
source/gui/sprite/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .Sprite import Sprite
|
102
source/gui/widget/Button.py
Normal file
102
source/gui/widget/Button.py
Normal 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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
from .Text import Text
|
from .Text import Text
|
||||||
from .FPSDisplay import FPSDisplay
|
from .FPSDisplay import FPSDisplay
|
||||||
|
from .Button import Button
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
33
source/utils/dict.py
Normal 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()
|
||||||
|
}
|
Loading…
Reference in a new issue