Implemented new basic button widget with simple sprite. A button can be hovered over...
authorStan_Lewry <stanley.jml@gmail.com>
Fri, 23 Jul 2021 13:54:01 +0000 (14:54 +0100)
committerStan_Lewry <stanley.jml@gmail.com>
Fri, 23 Jul 2021 13:54:01 +0000 (14:54 +0100)
Events are queued by the button and popped in main where they are handled. Eventually this can be moved to the Game class or whatever. Could create something where other classes can subscribe to certain events or something.
Can specify position, font, text, colour, and the event to emit on click in the JSON.
Re worked fonts in Texture.py to support multiple fonts at multiple sizes.
Changes to InputHandler to support mouse events.
screenshots for writeup.

Assets/GUIScenes/TestScene.json
Assets/basicButton.png [new file with mode: 0644]
GUI/BasicButtonWidget.py [new file with mode: 0644]
GUI/GuiManager.py
GUI/Widget.py
InputHandler.py
Main.py
Texture.py
Writeup/guitest.png [new file with mode: 0644]
Writeup/guitest2.png [new file with mode: 0644]

index 76c1329..dba393d 100644 (file)
@@ -1,23 +1,51 @@
 {
     "widgets": [
         {
-            "type" : "ImageWidget",
+            "type" : "BasicButton",
+            "props" : {
+                "x" : 1000,
+                "y" : 600,
+                "w" : 256,
+                "h" : 64,
+                "event" : "quit game",
+                "text" : "Exit",
+                "font" : "m12",
+                "fontSize" : "medium",
+                "colour" : "255, 255, 255"
+            }
+        },
+        {
+            "type" : "BasicButton",
+            "props" : {
+                "x" : 1000,
+                "y" : 664,
+                "w" : 256,
+                "h" : 64,
+                "event" : "",
+                "text" : "Test",
+                "font" : "m12",
+                "fontSize" : "medium",
+                "colour" : "255, 255, 255"
+            }
+        },
+        {
+            "type" : "Image",
             "props" : {
                 "source" : "smile.png",
-                "x" : 100,
-                "y" : 100,
-                "w" : 400,
-                "h" : 500,
+                "x" : 30,
+                "y" : 60,
+                "w" : 128,
+                "h" : 128,
                 "fit" : true
             }
         },
         {
-            "type": "ImageWidget",
+            "type": "Image",
             "props" : {
-                "w" : 250,
-                "h" : 250,
-                "x" : 1200,
-                "y" : 600,
+                "w" : 200,
+                "h" : 200,
+                "x" : 1700,
+                "y" : 800,
                 "source" : "test.png",
                 "fit" : true
             }
diff --git a/Assets/basicButton.png b/Assets/basicButton.png
new file mode 100644 (file)
index 0000000..dc52507
Binary files /dev/null and b/Assets/basicButton.png differ
diff --git a/GUI/BasicButtonWidget.py b/GUI/BasicButtonWidget.py
new file mode 100644 (file)
index 0000000..d8531da
--- /dev/null
@@ -0,0 +1,61 @@
+import GUI.Widget as Widget
+import Texture
+from enum import Enum
+import pygame
+import os
+
+BUTTONSTATE_IDLE = 0
+BUTTONSTATE_HOVER = 1
+BUTTONSTATE_CLICK = 2
+
+class BasicButtonWidget(Widget.Widget):
+    def __init__(self, gui, x, y, w, h, event, text, font, fontSize, colour) -> None:
+        super().__init__(gui)
+
+        self.event = event
+
+        self.screenX = x
+        self.screenY = y
+        self.screenW = w
+        self.screenH = h
+
+        self.text = text
+        self.font = font
+        self.fontSize = fontSize
+        self.colour = colour
+
+        self.surface = pygame.Surface((self.screenW, self.screenH))
+        self.buttonImage = pygame.image.load(os.path.join('Assets', 'basicButton.png'))
+        self.buttonImage = pygame.transform.scale(self.buttonImage, (self.screenW * 3, self.screenH))
+        self.setSurface(BUTTONSTATE_IDLE)
+
+    def setHover(self, hover) -> None:
+        if not hover == self.hover:
+            self.hover = hover
+            if self.hover:
+                self.setSurface(BUTTONSTATE_HOVER)
+            else:
+                self.setSurface(BUTTONSTATE_IDLE)
+
+    def setClicked(self, click) -> None:
+        if not click == self.click:
+            self.click = click
+            self.hover = False
+            if self.click:
+                self.setSurface(BUTTONSTATE_CLICK)
+                self._onClicked()
+            else:
+                self.setSurface(BUTTONSTATE_IDLE)
+
+    def setSurface(self, buttonState) -> None:
+        sourceRect = (buttonState * self.screenW, 0, self.screenW, self.screenH)
+        self.surface.blit(self.buttonImage, (0,0), sourceRect)
+        text = Texture.fontDict[self.textSize].render(self.text, False, self.textCol)
+        textRect = text.get_rect()
+        textOriginX = (self.screenW / 2) - (textRect.w / 2)
+        textOriginY = (self.screenH / 2) - (textRect.h / 2)
+        self.surface.blit(text, (textOriginX, textOriginY))
+
+    def _onClicked(self) -> None:
+        self._emit(self.event)
+        pass
\ No newline at end of file
index 848f967..d3593cc 100644 (file)
@@ -1,6 +1,7 @@
 import json
 from dataclasses import dataclass
 import GUI.ImageWidget as ImageWidget
+import GUI.BasicButtonWidget as BasicButtonWidget
 
 @dataclass
 class Event:
@@ -8,6 +9,7 @@ class Event:
 
 class GuiManager:
     def __init__(self) -> None:
+        # maybe make this a dictionary so they can have ids :)
         self.widgetList = []
         self.eventList = []
     
@@ -19,7 +21,7 @@ class GuiManager:
         with open(path, "r") as file:
             data = json.loads(file.read())
             for widget in data["widgets"]:
-                if widget["type"] == "ImageWidget" :
+                if widget["type"] == "Image" :
                     props = widget["props"]
                     self.createImageWidget(
                         props["x"],
@@ -29,14 +31,41 @@ class GuiManager:
                         props["source"],
                         props["fit"]
                     )
+                elif widget["type"] == "BasicButton" :
+                    props = widget["props"]
+                    self.createBasicButtonWidget(
+                        props["x"],
+                        props["y"],
+                        props["w"],
+                        props["h"],
+                        props["event"],
+                        props["text"],
+                        props["font"],
+                        props["fontSize"],
+                        tuple(map(int, props["colour"].split(', ')))
+                    )
+
     
     def createImageWidget(self, x, y, w, h, path, fit) -> None:
         self.widgetList.append(ImageWidget.ImageWidget(self, x, y, w, h, path, fit))
 
-    def mouseClicked(self, mouseX, mouseY) -> None:
-        # look through all widgets and see if the mouse hit it, if so call it's
-        # onClicked function
-        pass
+    def createBasicButtonWidget(self, x, y, w, h, event, text, font, fontSize, colour) -> None:
+        self.widgetList.append(BasicButtonWidget.BasicButtonWidget(self, x, y, w, h, event, text, font, fontSize, colour))
+
+    def handleMouseEvent(self, mouseX, mouseY, mouseDown) -> None:
+        for widget in self.widgetList:
+            if (mouseX > widget.screenX
+            and mouseX < widget.screenX + widget.screenW
+            and mouseY > widget.screenY
+            and mouseY < widget.screenY + widget.screenH):
+                if (mouseDown):
+                    widget.setClicked(True)
+                else:
+                    widget.setClicked(False)
+                    widget.setHover(True)
+            else:
+                widget.setHover(False)
+
     
     def registerEvent(self, event) -> None:
         self.eventList.append(event)
\ No newline at end of file
index 96f227c..41451b5 100644 (file)
@@ -12,9 +12,17 @@ class Widget:
         self.screenY = 0
         self.width = 0
         self.height = 0
+        self.hover = False
+        self.click = False
     
-    def onClicked(self) -> None:
+    def setClicked(self, click) -> None:
         pass
 
-    def emit(self, emitString) -> None:
-        self.guiManager.registerEvent(GuiManager.Event(emitString))
\ No newline at end of file
+    def setHover(self, hover) -> None:
+        pass
+
+    def _emit(self, emitString) -> None:
+        self.guiManager.registerEvent(GuiManager.Event(emitString))
+
+    def _onClicked(self) -> None:
+        pass
\ No newline at end of file
index 5d51aff..e755f1d 100644 (file)
@@ -11,6 +11,8 @@ class InputState:
     debugLevel: int = 0
     inputMade: bool = False
     debugRegenWorld: bool = False
+    mousePos: Tuple = (0, 0)
+    lmbDown: bool = False
 
 maxZoom = 3.0
 minZoom = 0.125
@@ -47,7 +49,13 @@ def handleInputs():
                 else: newInputState.debugLevel = 0
             elif event.key == pygame.K_F4:
                 newInputState.debugRegenWorld = True
+        elif event.type == pygame.MOUSEBUTTONDOWN:
+            newInputState.lmbDown = True
+        elif event.type == pygame.MOUSEBUTTONUP:
+            newInputState.lmbDown = False
     
+    newInputState.mousePos = pygame.mouse.get_pos()
+
     newInputState.inputMade = not newInputState == globalInputState
     # now replace the global one with our new one
     # mutex lock if I move to thread
diff --git a/Main.py b/Main.py
index f672bfe..5096b22 100644 (file)
--- a/Main.py
+++ b/Main.py
@@ -23,11 +23,13 @@ def main():
 
     player = Player.Player(World.worldWidth / 2, World.worldHeight / 2)
 
+    running = True
+
     # begin game loop
     getTicksLastFrame = 0
     t = 0
     deltaTime = 0
-    while not InputHandler.globalInputState.quit:
+    while running:
         # Compute delta time
         t = pygame.time.get_ticks()
         deltaTime = (t - getTicksLastFrame) / 1000.0
@@ -36,6 +38,15 @@ def main():
         Debug.debugRects.clear()
 
         InputHandler.handleInputs()
+        running  = not InputHandler.globalInputState.quit
+
+        guiManager.handleMouseEvent(*InputHandler.globalInputState.mousePos, InputHandler.globalInputState.lmbDown)
+
+        # handle event queue
+        while guiManager.eventList:
+            event = guiManager.eventList.pop()
+            if event.event == "quit game":
+                running = False
 
         player.update()
         
index c2a2bf4..a9fddc1 100644 (file)
@@ -7,6 +7,20 @@ spriteSheet = pygame.image.load(os.path.join('Assets', 'doomSheet.png'))
 animationSheet = pygame.image.load(os.path.join('Assets', 'animation.png'))
 font = pygame.font.Font(os.path.join('Assets','m12.ttf'), 28)
 
+#smallFont = pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 12)
+# = pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 12)
+
+m12FontDict = {
+    "small" : pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 12),
+    "medium" : pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 20),
+    "large" : pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 28),
+    "veryLarge" : pygame.font.Font(os.path.join('Assets', 'm12.ttf'), 32)
+}
+
+fontDictDict = {
+    "m12" : m12FontDict
+}
+
 sheetWidth = spriteSheet.get_width()
 sheetHeight = spriteSheet.get_height()
 # multiply coords by sprite size :)
diff --git a/Writeup/guitest.png b/Writeup/guitest.png
new file mode 100644 (file)
index 0000000..aa808c3
Binary files /dev/null and b/Writeup/guitest.png differ
diff --git a/Writeup/guitest2.png b/Writeup/guitest2.png
new file mode 100644 (file)
index 0000000..076583d
Binary files /dev/null and b/Writeup/guitest2.png differ