Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to use Thread() ?
#7
Added in a clock display that makes it easier to see that the thread runs the countdown without blocking the pytgame event loop.
import pygame
import threading
import time

pygame.init()
FOREGROUND_COLOR = "White"
BACKGROUND_COLOR = "Black"
DEFAULT_FONT = pygame.font.SysFont(None, 36)
CLOCK_FONT = pygame.font.SysFont(None, 20)
screen = pygame.display.set_mode([390, 300])


class Countdown:
    """A countdown timer that calls a function when it counts down to zero"""
    def __init__(self, display):
        self.display = display
        self.callback = None
        self.counting = False

    def doit(self, start_count):
        """Does the countdown.  Runs in its own thread so it doesn't block."""
        self.counting = True
        for count in range(start_count, 0, -1):
            if not self.counting:
                break
            self.display.set_text(f"Countdown: {count}")
            time.sleep(1)
        else:
            self.counting = False
            if self.callback is not None:
                self.callback()

    def connect(self, func):
        """Specify the function called when the timer counts down to zero"""
        self.callback = func

    def start(self, start_count):
        """Start the countdown"""
        if not self.counting:
            threading.Thread(target=self.doit, args=(start_count,)).start()

    def stop(self):
        """Stop the countdown"""
        self.counting = False
        self.display.set_text("")


class Widget(pygame.surface.Surface):
    """Base class for pygame widgets"""
    def __init__(self, parent, size, foreground=None, background=None):
        super().__init__(size)
        self.parent = parent
        self.foreground = FOREGROUND_COLOR if foreground is None else foreground
        self.background = BACKGROUND_COLOR if background is None else background
        self.rect = self.get_rect()

    def draw(self):
        """Draw (blit) self on parent surface."""
        self.parent.blit(self, (self.rect.x, self.rect.y))
        return self

    def erase(self):
        """Draw (blit) background color on parent surface"""
        pygame.draw.rect(self.parent, BACKGROUND_COLOR, self.rect)
        return self

    def at(self, x, y):
        """Set upper left corner at x, y"""
        self.rect.x = x
        self.rect.y = y
        return self

    def center_at(self, x, y):
        """Set center at x, y"""
        self.rect.centerx = x
        self.rect.centery = y
        return self

    @property
    def x(self):
        return self.rect.x

    @x.setter
    def x(self, new_x):
        self.rect.x = new_x

    @property
    def y(self):
        return self.rect.y

    @y.setter
    def y(self, new_y):
        self.rect.y = new_y

    @property
    def width(self):
        return self.rect.width

    @width.setter
    def width(self, new_width):
        self.rect.width = new_width

    @property
    def height(self):
        return self.rect.height

    @height.setter
    def height(self, new_height):
        self.rect.height = new_height


class Label(Widget):
    """A tkinter Label like thing for pygame"""
    def __init__(self, parent, text, width=None, font=None, foreground=None, background=None):
        width = width or len(text)
        self.font = DEFAULT_FONT if font is None else font
        size = self.font.render("W"*width, True, BACKGROUND_COLOR).get_size()
        super().__init__(parent, size, foreground, background)
        self.set_text(text)

    def set_text(self, text):
        self.text = text
        text_img = self.font.render(self.text, True, self.foreground)
        y = (self.rect.height - text_img.get_height()) // 2
        self.fill(self.background)
        self.blit(text_img, (0, y))
        self.draw()
        return self


class CircleButton(Widget):
    """A tkinter Button like think for pygame"""
    buttons = []

    @classmethod
    def clicked(cls, x, y):
        """Call this method to find what button was pressed"""
        for button in cls.buttons:
            if button.click(x, y):
                return True
        return False

    def __init__(self, parent, text, radius, foreground=None, text_color=None, font=None):
        super().__init__(parent, (radius*2, radius*2), foreground)
        self.font = DEFAULT_FONT if font is None else font
        self.text_color = self.background if text_color is None else text_color
        self.callback = None
        self.buttons.append(self)
        self.set_text(text)

    def set_text(self, text):
        """Set my label text.  Label is centered in button"""
        self.text = text
        text_img = self.font.render(self.text, True, self.text_color)
        x = (self.rect.width - text_img.get_width()) // 2
        y = (self.rect.height - text_img.get_height()) // 2
        self.fill(self.background)
        pygame.draw.ellipse(self, self.foreground, self.get_rect())
        self.blit(text_img, (x, y))
        return self

    def connect(self, func):
        """Set function to call when clicked"""
        self.callback = func
        return self

    def click(self, x, y):
        """Check if I was clicked.  Execute callback method if clicked"""
        clicked = self.rect.collidepoint(x, y)
        if clicked and self.callback:
            self.callback(self)
        return clicked

# Give the buttons something to do
def button_pressed(button):
    display.set_text(display.text + button.text)

clock_display = Label(screen, "", width=8, font=CLOCK_FONT).at(300, 20)
display = Label(screen, "", width=10, foreground="black", background="white")
display.erase().at(20, 20).draw()

# Create a countdown timer that goes BOOM!! when it counts all the way down.
countdown = Countdown(display)
countdown.connect(lambda: display.set_text("BOOM!!"))

# Make some buttons
for number in range(9):
    x = 20 + (number % 3) * 80
    y = 220 - (number // 3) * 80
    CircleButton(screen, str(number+1), 30, "Blue", "White") \
        .connect(button_pressed).at(x, y).draw()

CircleButton(screen, "Start", 50, "Green") \
    .connect(lambda x: countdown.start(10)).at(270, 60).draw()

CircleButton(screen, "Stop", 50, "Red") \
    .connect(lambda x: countdown.stop()).at(270, 180).draw()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            break

        if event.type == pygame.MOUSEBUTTONUP:
            CircleButton.clicked(*pygame.mouse.get_pos())

    clock_display.set_text(time.strftime("%H:%M:%S", time.localtime()))
    pygame.display.flip()
    pygame.display.update()

pygame.quit()
Frankduc likes this post
Reply


Messages In This Thread
How to use Thread() ? - by Frankduc - May-10-2022, 05:42 PM
RE: How to use Thread() ? - by deanhystad - May-10-2022, 07:00 PM
RE: How to use Thread() ? - by Frankduc - May-10-2022, 07:11 PM
RE: How to use Thread() ? - by deanhystad - May-10-2022, 08:53 PM
RE: How to use Thread() ? - by Frankduc - May-10-2022, 09:53 PM
RE: How to use Thread() ? - by deanhystad - May-12-2022, 12:23 PM
RE: How to use Thread() ? - by deanhystad - May-13-2022, 07:06 PM
RE: How to use Thread() ? - by Frankduc - May-17-2022, 04:51 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Error SQLite objects created in a thread can only be used in that same thread. binhduonggttn 3 15,737 Jan-31-2020, 11:08 AM
Last Post: DeaD_EyE

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020