diff --git a/assets/image/boat/boat_body.png b/assets/image/boat/boat_body.png deleted file mode 100644 index 1b44c3f..0000000 Binary files a/assets/image/boat/boat_body.png and /dev/null differ diff --git a/assets/image/boat/boat_broken.png b/assets/image/boat/boat_broken.png deleted file mode 100644 index 41fcb17..0000000 Binary files a/assets/image/boat/boat_broken.png and /dev/null differ diff --git a/assets/image/boat/boat_edge.png b/assets/image/boat/boat_edge.png deleted file mode 100644 index ab9e4ed..0000000 Binary files a/assets/image/boat/boat_edge.png and /dev/null differ diff --git a/assets/image/button/test_button_clicking.png b/assets/image/button/test_button_clicking.png deleted file mode 100644 index 13a3ed3..0000000 Binary files a/assets/image/button/test_button_clicking.png and /dev/null differ diff --git a/assets/image/button/test_button_hover.png b/assets/image/button/test_button_hover.png deleted file mode 100644 index 89cbda0..0000000 Binary files a/assets/image/button/test_button_hover.png and /dev/null differ diff --git a/assets/image/button/test_button_normal.png b/assets/image/button/test_button_normal.png deleted file mode 100644 index 706e5f1..0000000 Binary files a/assets/image/button/test_button_normal.png and /dev/null differ diff --git a/main.pyw b/main.pyw index a6f202a..020482f 100644 --- a/main.pyw +++ b/main.pyw @@ -1,7 +1,7 @@ import pyglet from source.gui.scene.abc import Scene -from source.gui.widget import Text, FPSDisplay, Button +from source.gui.widget import FPSDisplay, Button, Image, Input from source.gui.window import Window # Test Scene @@ -13,30 +13,45 @@ class TestScene(Scene): # loading resource - texture_normal = pyglet.image.load("./assets/image/button/test_button_normal.png") - texture_hover = pyglet.image.load("./assets/image/button/test_button_hover.png") - texture_click = pyglet.image.load("./assets/image/button/test_button_clicking.png") + texture_button_normal = pyglet.image.load("./assets/image/button/normal.png") + texture_button_hover = pyglet.image.load("./assets/image/button/hovering.png") + texture_button_click = pyglet.image.load("./assets/image/button/clicking.png") + texture_input_normal = pyglet.image.load("./assets/image/input/inputbox.png") + texture_input_active = pyglet.image.load("./assets/image/input/active.png") + texture_input_error = pyglet.image.load("./assets/image/input/error.png") - button_atlas = pyglet.image.atlas.TextureAtlas() - region_normal = button_atlas.add(texture_normal) - region_hover = button_atlas.add(texture_hover) - region_click = button_atlas.add(texture_click) + texture_atlas = pyglet.image.atlas.TextureAtlas() + region_button_normal = texture_atlas.add(texture_button_normal) + region_button_hover = texture_atlas.add(texture_button_hover) + region_button_click = texture_atlas.add(texture_button_click) + region_input_normal = texture_atlas.add(texture_input_normal) + region_input_active = texture_atlas.add(texture_input_active) + region_input_error = texture_atlas.add(texture_input_error) self.background_batch = pyglet.graphics.Batch() self.label_batch = pyglet.graphics.Batch() # the widgets - self.fps_display = self.add_widget(FPSDisplay) + self.fps_display = self.add_widget(FPSDisplay, color=(255, 255, 255, 127)) + + background = self.add_widget( + Image, + + x=0, y=0, width=1, height=1, + + image=region_input_normal, + batch=self.background_batch, + ) label = self.add_widget( Button, x=0.5, y=0.5, width=0.5, height=0.5, - texture_normal=region_normal, - texture_hover=region_hover, - texture_click=region_click, + texture_normal=region_button_normal, + texture_hover=region_button_hover, + texture_click=region_button_click, label_text="Hello World !", @@ -44,15 +59,37 @@ class TestScene(Scene): label_batch=self.label_batch, ) - label.on_pressed = lambda button, modifiers: print("pressed", label, button, modifiers) + label.on_pressed = lambda button, modifiers: window.set_scene(TestScene2) label.on_release = lambda button, modifiers: print("release", label, button, modifiers) + input_ = self.add_widget( + Input, + + x=0.1, y=0.2, width=0.4, height=0.1, + + texture_normal=region_input_normal, + texture_active=region_input_active, + texture_error=region_input_error, + + # 4 numéros de 1 à 3 chiffres aséparés par des points (IP), optionnellement suivi + # de deux points ainsi que de 1 à 5 chiffres (port) + regex=r"\d{1,3}(\.\d{1,3}){3}(:\d{1,5})?", + + background_batch=self.background_batch, + label_batch=self.label_batch, + ) + def on_draw(self): self.background_batch.draw() self.label_batch.draw() self.fps_display.draw() +class TestScene2(Scene): + def __init__(self, window: "Window"): + super().__init__(window) + + # Create a new window window = Window(resizable=True, vsync=False) window.add_scene(TestScene) diff --git a/source/gui/widget/Button.py b/source/gui/widget/Button.py index 06ec010..a13b44c 100644 --- a/source/gui/widget/Button.py +++ b/source/gui/widget/Button.py @@ -42,6 +42,7 @@ class Button(BoxWidget): ) self.label = pyglet.text.Label( + width=None, height=None, anchor_x="center", anchor_y="center", **dict_prefix("label_", kwargs) ) @@ -71,11 +72,10 @@ class Button(BoxWidget): self.background.image = self.background_texture def _refresh_size(self) -> None: - self.background.x = self.x - self.background.y = self.y - self.background.width = self.width - self.background.height = self.height + self.background.x, self.background.y = self.x, self.y + self.background.width, self.background.height = self.width, self.height + # center the label self.label.x = self.x + (self.width / 2) self.label.y = self.y + (self.height / 2) diff --git a/source/gui/widget/FPSDisplay.py b/source/gui/widget/FPSDisplay.py index b8560aa..5ce6726 100644 --- a/source/gui/widget/FPSDisplay.py +++ b/source/gui/widget/FPSDisplay.py @@ -13,10 +13,10 @@ class FPSDisplay(Widget): A widget that display the current FPS of the scene's window """ - def __init__(self, scene: "Scene"): + def __init__(self, scene: "Scene", *args, **kwargs): super().__init__(scene) - self.fps_display = pyglet.window.FPSDisplay(scene.window) + self.fps_display = pyglet.window.FPSDisplay(scene.window, *args, **kwargs) def draw(self): self.fps_display.draw() diff --git a/source/gui/widget/Image.py b/source/gui/widget/Image.py new file mode 100644 index 0000000..ad517b7 --- /dev/null +++ b/source/gui/widget/Image.py @@ -0,0 +1,43 @@ +from typing import TYPE_CHECKING + +import pyglet.image + +from source.gui.sprite import Sprite +from source.gui.widget.abc import BoxWidget +from source.type import Percentage + + +if TYPE_CHECKING: + from source.gui.scene.abc import Scene + + +class Image(BoxWidget): + def __init__(self, scene: "Scene", + + image: pyglet.image.AbstractImage, + + x: Percentage = 0, + y: Percentage = 0, + width: Percentage = None, + height: Percentage = None, + *args, **kwargs): + super().__init__(scene, x, y, width, height) + + self.image = Sprite(img=image, *args, **kwargs) + + self._refresh_size() + + # refresh + + def _refresh_size(self): + self.image.width, self.image.height = self.width, self.height + + # event + + def on_resize(self, width: int, height: int): + self._refresh_size() + + # draw + + def draw(self): + self.image.draw() diff --git a/source/gui/widget/Input.py b/source/gui/widget/Input.py new file mode 100644 index 0000000..174520f --- /dev/null +++ b/source/gui/widget/Input.py @@ -0,0 +1,115 @@ +import re +from typing import TYPE_CHECKING, Optional + +import pyglet.image + +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 Input(BoxWidget): + def __init__(self, scene: "Scene", + + texture_normal: pyglet.image.AbstractImage, + texture_active: pyglet.image.AbstractImage = None, + texture_error: pyglet.image.AbstractImage = None, + + regex: Optional[str | re.Pattern] = None, + + x: Percentage = 0, + y: Percentage = 0, + width: Percentage = None, + height: Percentage = None, + *args, **kwargs): + super().__init__(scene, x, y, width, height) + + self._texture_normal: pyglet.image.AbstractImage = texture_normal + self._texture_active: Optional[pyglet.image.AbstractImage] = texture_active + self._texture_error: Optional[pyglet.image.AbstractImage] = texture_error + + self._invalid = False + + self.regex = re.compile(regex) if isinstance(regex, str) else regex + + self.background = Sprite( + img=self._texture_normal, + **dict_prefix("background_", kwargs) + ) + self.label = pyglet.text.Label( + width=None, height=None, + anchor_x="center", anchor_y="center", + **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_active if self.activated and self._texture_active is not None else + self._texture_error if self.invalid and self._texture_error is not None else + self._texture_normal + ) + + # refresh + + def _refresh_background(self) -> None: + self.background.image = self.background_texture + + def _refresh_size(self) -> None: + self.background.x, self.background.y = self.x, self.y + self.background.width, self.background.height = self.width, self.height + + # center the label + self.label.x = self.x + (self.width / 2) + 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 + def invalid(self): + return self._invalid + + @invalid.setter + def invalid(self, invalid: bool): + self._invalid = invalid + self._refresh_background() + + # event + + def on_key_press(self, symbol: int, modifiers: int): + if not self.activated: return # ignore si ce widget est désactivé / non sélectionné + + if symbol == pyglet.window.key.BACKSPACE: # si la touche "supprimé" est enfoncé + self.label.text = self.label.text[0:-1] # retire le dernier caractère du texte + + def on_text(self, char: str): + if not self.activated: return # ignore si ce widget est désactivé / non sélectionné + self.label.text += char # ajoute le caractère au label + + if self.regex is not None: # si il y a un regex de validation, applique le pour vérifier le texte + self.invalid = self.regex.fullmatch(self.label.text) is None + + def on_resize(self, width: int, height: int): + self._refresh_size() + + def draw(self): + self.background.draw() + self.label.draw() diff --git a/source/gui/widget/__init__.py b/source/gui/widget/__init__.py index 4b0ba0b..b0432a8 100644 --- a/source/gui/widget/__init__.py +++ b/source/gui/widget/__init__.py @@ -1,3 +1,5 @@ from .Text import Text from .FPSDisplay import FPSDisplay from .Button import Button +from .Input import Input +from .Image import Image diff --git a/source/gui/widget/abc/BoxWidget.py b/source/gui/widget/abc/BoxWidget.py index 2e442f9..94a0af5 100644 --- a/source/gui/widget/abc/BoxWidget.py +++ b/source/gui/widget/abc/BoxWidget.py @@ -29,6 +29,7 @@ class BoxWidget(Widget, ABC): self._hovering = False # is the button currently hovered ? self._clicking = False # is the button currently clicked ? + self._activated = False # is the button activated ? (the last click was inside this widget) # property @@ -94,6 +95,14 @@ class BoxWidget(Widget, ABC): def clicking(self, clicking: bool): self._clicking = clicking + @property + def activated(self): + return self._activated + + @activated.setter + def activated(self, activated: bool): + self._activated = activated + # event def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): # NOQA @@ -125,15 +134,19 @@ class BoxWidget(Widget, ABC): 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 - if not in_bbox((x, y), self.bbox): return - self.clicking = True + if not in_bbox((x, y), self.bbox): + self.activated = False # if the click was not in the bbox, disable the activated state + return + + self.activated = True # if the click is inside the bbox, enable the activated state + self.clicking = True # the widget is now clicked 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 # the widget is no longer clicked if not in_bbox((x, y), self.bbox): return