From 48da47c2344bc46b4756b34f084d938b5918a447 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 14 Feb 2023 10:36:59 +0100 Subject: [PATCH] added Image and Input widgets --- assets/image/boat/boat_body.png | Bin 180 -> 0 bytes assets/image/boat/boat_broken.png | Bin 892 -> 0 bytes assets/image/boat/boat_edge.png | Bin 747 -> 0 bytes assets/image/button/test_button_clicking.png | Bin 224 -> 0 bytes assets/image/button/test_button_hover.png | Bin 224 -> 0 bytes assets/image/button/test_button_normal.png | Bin 224 -> 0 bytes main.pyw | 63 +++++++--- source/gui/widget/Button.py | 8 +- source/gui/widget/FPSDisplay.py | 4 +- source/gui/widget/Image.py | 43 +++++++ source/gui/widget/Input.py | 115 +++++++++++++++++++ source/gui/widget/__init__.py | 2 + source/gui/widget/abc/BoxWidget.py | 19 ++- 13 files changed, 232 insertions(+), 22 deletions(-) delete mode 100644 assets/image/boat/boat_body.png delete mode 100644 assets/image/boat/boat_broken.png delete mode 100644 assets/image/boat/boat_edge.png delete mode 100644 assets/image/button/test_button_clicking.png delete mode 100644 assets/image/button/test_button_hover.png delete mode 100644 assets/image/button/test_button_normal.png create mode 100644 source/gui/widget/Image.py create mode 100644 source/gui/widget/Input.py diff --git a/assets/image/boat/boat_body.png b/assets/image/boat/boat_body.png deleted file mode 100644 index 1b44c3fffa6e513f5d83893f52acdd15dc42304c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!pr@ycV@QPi+j9qb85DV#9i6Q_W@l=sF5?!s zv$dgO!|zRhW-VL)@LdPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0})9?K~z{r?Uvt5 zlTjGQAGaA|!yhAIMvB;wfg$rkyl_Mp!P0o=&lGzFoc1d& zmrKIou=Mu!N`HU9w6?Yi1|l098_=^+J>GS!FX+H|| z0W?83gXZRDUP`jQzHUbH&AeK{#e55XhFr9a*_KBi0QdP#2Jv`YmX?+n?gwsFc06vz z9y4Z}4;mD%f!FZC?(VKkO-;$x)>gLF0zOlQtI*?gIwcSY$l&0hG&OOOje%TeK&-Z5 z42HJ1w@)XNN$KwH&b9*1%KZGiOixeC@-ipP;5oE<4R@h87z|2ZU!Sps8yg#CVPS!v zr6Ip(xeW;v)%5-NBrH$+-Ks5cv&N!|Qzd S3Q>{(0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0)RTh$ooEnWrYA_fmaf18{iNr1( zBH%lh>2#_xnT$%O(`vC;=r08QT@sQCpvpU{pUq}&M!*Tr1(G;qO;Q0rxlnyRpVtY% z37-r4Z)`~-K=;^j95tCt+>DRmaHw!b&@;lqh9m-Xf3dqzDCmTrY&NT%@%ZN5W3d%* zBk`K*eO5n$xey_|k}oB2EEZb~!y0=-~OZou>3t9ppm0v$)D-E$J^`O3vfXZ1)$4UPlK>pU zIRO`VRwz*do=bdYv0AOT9RzdZx$r>(M;{R-;H$*dq1793q2KSbWRDL0r3@25^$0kq z_d(|Kxwi^;Axr=V%&S(brP}Sbn>hj;#7O}c5b=PZfSpyx^gf91S!`-Jydx+;+th3} zPg1=B7do8|%Uyd7egV7ceSJF_zGvcsK0bk80NduZpnAQYn>isk%3KM!@Qwu-0lO{p zl)Vq)dp1t%{{r#IIyM^!ezH=*Iy!+za7kMS-yQGQYZmg?c fz3u(xb3YkxOIap1U(S07w2r~k)z4*}Q$iB}Hw9LK diff --git a/assets/image/button/test_button_hover.png b/assets/image/button/test_button_hover.png deleted file mode 100644 index 89cbda08f614f10cbf2ee70081ccebe95739d0ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmeAS@N?(olHy`uVBq!ia0y~yU@QZ&omiNGo2h#k()weUVpUzDt@Y?#8i(OQ3I2Snb5g&r{k84={hKAbh41#hb1PrG`{Lafc_rq%q>18gtgBzW e?fvF+KN)XJStd4L&U*;7j=|H_&t;ucLK6T}kyw)e 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