Problem: pygame Button Class

  • Moin.

    Bin dabei eine Button Class zu erstellen um auf flexible Art und Weise eben Schaltflächen zu erstellen und diese zu behandeln.

    Mein Problem ist das definieren einer Action bzw Funktion die beim Klicken auf den jeweiligen Button ausgeführt werden soll :helpnew:

    Also das python2 Grundgerüst sieht wie folgt aus:

    => http://codepad.org/d8FWbE5L
    [code=php]
    from sys import exit
    from pygame.locals import *
    import pygame

    class Button:
    hovered = False
    def __init__(self, _font=None, fontsize=21, text="", pos=(0, 0), func=None, screen=None):
    try:
    self.font = pygame.font.Font(_font, fontsize)
    except:
    self.font = pygame.font.SysFont(_font, fontsize)
    self.function = func
    self.screen = screen
    self.text = text
    self.pos = pos
    self.default_color = (100, 100, 100) #gray
    self.hover_color = (255, 255, 255) #white
    self.set_rect()
    self.draw()

    def draw(self):
    self.set_rend()
    pygame.display.update( self.screen.blit(self.rend, self.rect) )

    def get_color(self):
    if self.hovered:
    return self.hover_color # white
    else:
    return self.default_color # gray

    def set_rend(self):
    self.rend = self.font.render(self.text, True, self.get_color())

    def set_rect(self):
    self.set_rend()
    self.rect = self.rend.get_rect()
    self.rect.topleft = self.pos

    def action(self): # FIXME!
    eval( self.func )


    if __name__ == '__main__':

    buttonHover = True
    FPS = 30

    width = 640
    height = 480

    # colors R G B
    white = (255, 255, 255)
    red = (255, 0, 0)
    green = ( 0, 255, 0)
    blue = ( 0, 0, 255)
    black = ( 0, 0, 0)
    cyan = ( 50, 255, 255)
    magenta = (255, 0, 255)
    yellow = (255, 255, 0)
    orange = (255, 127, 0)
    gray = (100, 100, 100)

    size = (width, height)

    try:
    pygame.init()
    window = pygame.display.set_mode(size)
    window.fill(black)

    buttons = [
    Button(text="Start", _font="droidsans", fontsize=42, pos=(30, 0), screen=window),
    Button(text="Stop", _font="droidsans", fontsize=42, pos=(30, 60), screen=window),
    ]

    clock = pygame.time.Clock()
    while True:
    clock.tick(FPS)
    for event in pygame.event.get():
    if event.type in (QUIT, KEYDOWN):
    exit()
    elif event.type == pygame.MOUSEBUTTONDOWN:
    for button in buttons:
    if button.rect.collidepoint(pygame.mouse.get_pos()):
    print button.text

    if buttonHover:
    for button in buttons:
    if button.rect.collidepoint(pygame.mouse.get_pos()):
    button.hovered = True
    button.draw()
    else:
    button.hovered = False
    button.draw()


    except (KeyboardInterrupt, SystemExit):
    print 'Quit'
    pygame.quit()
    [/php]

    "action" wäre das womit ich noch Probleme habe - das was da steht war nur ein verzweifelter Versuch.... Ich möchte da halt eine Function definieren können - nur wie? :s

    Der Rest funktioniert soweit super; beim Klicken auf einer der Schaltflächen wird auch der jeweils passende Text ausgegeben.
    Beim erstellen der Button Objekte fehlt noch die Übergabe von "func"


    Weiß jemand wie ich das lösen kann?

  • Das sollte es tun.

    Nur ein Tipp: ein display update in einem beliebigen Szenen-Objekt ist glaube ich ein Irrweg. Von Button.draw das betroffene rectangle zurückliefen, und die gesammelte Liste aller dieser Rects dann in *einem* call auszuwerten bedeutet weniger Annahmen über die Optimierungsstrategie. Sonst fällt dir das mal auf die Füße.

  • Hm und was ist wenn ich als 'action' eine Methode eines anderen Objekts aufrufen möchte?

    Hintergrund: Ich möchte durch klicken auf "Start" den Live-Stream der RaspiCam starten und auf "Stop" wieder stoppen. Es geht quasi um den Code aus diesem Beitrag: https://www.forum-raspberrypi.de/Thread-raspbia…33781#pid233781

    Button(text="Start", func=PiVideoStream().start(), _font="droidsans", fontsize=42, pos=(30, 0), screen=window), ... wird dann denk ich eher nicht funktionieren oder?

    irgendwo hab ichn knoten ...

  • Grundsätzlich geht das genauso. Du musst halt eine "bound method" erzeugen. Das hier ist mit tapatalk, darum ohne Code Tags.

    foo = Foo()
    callback = foo.methode


    Damit ist Methode automatisch mit der Instanz foo als Self zu einer Funktion geworden.

    In deinem Fall aber musst du natürlich den Stream behalten:

    self.stream = PiVideoStream()
    callback = self.stream.start


    Gesendet von iPhone mit Tapatalk

  • @deets: Ja da kam ich zwischenzeitlich auch drauf - nachdem der Knoten im Kopf kurzzeitig weg war :lol:

    Allerdings läuft der Stream jetzt nicht mehr, die Methoden dafür werden zwar ausgeführt und zB. die LED an der RaspiCam geht auch an etc., aber es erscheint kein Bild :s

    => http://codepad.org/JzsbwRPN

    :wallbash:

  • Hm. Sieht so weit ok aus. Aber klappt's mit start ohne Button? Also direkt vor Loop Eintritt aufgerufen?


    Gesendet von iPhone mit Tapatalk

  • also. es würde mich sehr freuen, keiune kommentare wie:'' das ist voll scheisse!'' oder so zu lesen. das weis ich selbst. aber es fuktiniert. für eine verbesserte version bin ich natürlich offen.

    import pygame
    import time
    import random
    import math

    white = (255,255,255)
    grey = (105,105,105)
    black = (0,0,0)
    red = (250,0,0)
    green = (0,255,0)
    blue = (0,0,255)
    yellow = (255,255,0)
    background = black
    scalen = blue

    def dark(COLOR):
    Dark = (COLOR[0]/1.5,COLOR[1]/1.5,COLOR[2]/1.5)
    return Dark

    def text_objects(text, font, color):
    textSurface = font.render(text,True, color)
    return textSurface, textSurface.get_rect()
    def message_display(msg,color,size,posX,posY):
    gameDisplay = pygame.display.get_surface()
    largeText = pygame.font.Font("freesansbold.ttf",size)
    TextSurf, TextRect = text_objects(str(msg),largeText,color)
    TextRect.center = (posX,posY)
    gameDisplay.blit(TextSurf,TextRect)

    def button(color,posX,posY,text,txtclr,brei,hoe,act,actdef):
    gameDisplay = pygame.display.get_surface()
    event = pygame.event.poll()
    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()
    pygame.draw.rect(gameDisplay,dark(color),(posX,posY,brei,hoe))
    message_display(str(text),txtclr,20,posX+(brei/2),posY+(hoe/2) )
    if posX < mouse[0] < posX+brei and posY < mouse[1] < posY+hoe:
    pygame.draw.rect(gameDisplay,color,(posX,posY,brei,hoe))
    message_display(str(text),txtclr,20,posX+(brei/2),posY+(hoe/2) )
    if click[0] == 1:
    act(actdef)


    bsp:

    def Close_Icon():
    gameDisplay = pygame.display.get_surface()
    Rudi = pygame.Surface.get_size(gameDisplay)
    display_Hoehe = Rudi[1]
    display_Breite = Rudi[0]
    button(red,display_Breite-display_Hoehe/20,0,'X',white,display_Hoehe/20,display_Hoehe/20,X,1)
    pygame.display.update([display_Breite-display_Hoehe/20,0,display_Hoehe/20,display_Hoehe/20])
    def X(wert):
    if Wert == 1:
    pygame.quit()

    Close_Icon()

    jetzt mal ehrlich...
    Schlechte gehts nicht


  • Hm. Sieht so weit ok aus. Aber klappt's mit start ohne Button? Also direkt vor Loop Eintritt aufgerufen?

    Leider ja.

    Wenn ich Das mache:

    [code=php]
    try:
    pygame.init()
    window = pygame.display.set_mode(size)
    window.fill(black)
    pygame.display.set_caption("picamera stream on pygame")
    vs = PiVideoStream()
    vs.start()
    sleep(0.2)

    buttons = [
    Button(text="Start", callback=vs.start, _font="droidsans", fontsize=42, pos=(30, 0), screen=window),
    Button(text="Stop", callback=vs.stop, _font="droidsans", fontsize=42, pos=(30, 60), screen=window),
    ]

    for button in buttons:
    pygame.display.update(button.draw())

    clock = pygame.time.Clock()
    while True:
    clock.tick(FPS)
    ....[/php]

    also vs.start() vorher ausführe, dann funktioniert es und der Stream wird angezeigt. Sofort beim ausführen des Scripts.
    Klick ich dann auf "Stop" wird der Stream sogar gestoppt. Klick ich danach auf Start wird der Stream wieder gestartet. Aber wenn ich den Stream nicht direkt von Anfang an starte funktioniert das irgendwie nicht... :-/ :s :@

    Dirty Lösung meinerseits wäre
    [code=php]
    vs.start()
    sleep(0.2)
    vs.stop()
    [/php]


    morila: Bitte in CODE oder PHP Blöcke verpacken. Danke.


    //EDIT: Mit der Dirty Lösung sieht der Code jetzt wie folgt aus => http://codepad.org/NUK3NgE9

    [code=php]
    #!/usr/bin/python2.7
    #
    # Copyright (C) 2016 by meigrafd (meiraspi@gmail.com) published under the MIT License
    # v0.2
    #
    # display picamera stream on pygame
    #
    # http://www.pyimagesearch.com/2015/12/28/inc…hon-and-opencv/
    # https://www.snip2code.com/Snippet/979508…ng-on-PyGame-on
    #

    from __future__ import print_function
    from datetime import datetime
    from picamera.array import PiRGBArray
    from threading import Thread
    from pygame.locals import *
    from time import sleep, strftime
    from sys import exit, stdout
    import numpy as np
    import pygame
    import picamera


    def printD(message):
    if DEBUG:
    print('[{}] {}'.format(strftime('%H:%M:%S'), message))
    stdout.flush()


    class PiVideoStream(object):
    def __init__(self, resolution=(320, 240), format='rgb', framerate=30, led=True):
    self.camera_led = led
    self.camera = picamera.PiCamera()
    self.camera.resolution = resolution
    self.camera.framerate = framerate
    self.rawCapture = PiRGBArray(self.camera, size=resolution)
    self.stream = self.camera.capture_continuous(self.rawCapture, format=format, use_video_port=True)
    self.frame = None
    self.running = False

    def start(self):
    # start the thread to read frames from the video stream
    if self.camera_led:
    self.camera.led = self.camera_led
    self.running = True
    Thread(target=self.update, args=()).start()
    #return self

    def update(self):
    # keep looping infinitely until the thread is stopped
    for frameBuf in self.stream:
    # grab the frame from the stream and clear the stream in preparation for the next frame
    self.frame = np.rot90(frameBuf.array)
    self.rawCapture.truncate(0)
    # if the thread indicator variable is set, stop the thread and resource camera resources
    if self.running == False:
    self.camera.led = False
    return

    def quit(self):
    try:
    self.running = False
    self.stream.close()
    self.rawCapture.close()
    self.camera.close()
    except:
    pass

    def read(self):
    # return the frame most recently read
    return self.frame

    def stop(self):
    # indicate that the thread should be stopped
    self.running = False


    def update_stream_screen(videostream, screen):
    frame = pygame.surfarray.make_surface(videostream.read())
    return screen.blit(frame, stream_screen_pos)


    # draw button as object
    class Button(object):
    def __init__(self, _font=None, fontsize=21, text='', pos=(0, 0), callback=None, screen=None):
    if not _font:
    _font = 'freesansbold.ttf'
    try:
    self.font = pygame.font.Font(_font, fontsize)
    except:
    self.font = pygame.font.SysFont(_font, fontsize)
    self.font_antialias = True
    self.font_background = None
    self._callback = callback
    self.screen = screen
    self.text = text
    self.pos = pos
    self.default_color = (100, 100, 100) #gray
    self.hover_color = (255, 255, 255) #white
    self.hovered = False
    self.set_rect()

    def draw(self):
    self.set_rend()
    return self.screen.blit(self.rend, self.rect)

    def get_color(self):
    if self.hovered:
    return self.hover_color
    else:
    return self.default_color

    def set_rend(self):
    self.rend = self.font.render(self.text, self.font_antialias, self.get_color(), self.font_background)

    def set_rect(self):
    self.set_rend()
    self.rect = self.rend.get_rect()
    self.rect.topleft = self.pos

    def action(self):
    if self._callback:
    return self._callback()


    if __name__ == '__main__':

    DEBUG = True
    debugFPS = True
    buttonHover = True
    FPS = 30

    width = 640
    height = 480
    stream_screen_pos = (160, 160) # vertical, horizontal

    # colors R G B
    white = (255, 255, 255)
    red = (255, 0, 0)
    green = ( 0, 255, 0)
    blue = ( 0, 0, 255)
    black = ( 0, 0, 0)
    cyan = ( 50, 255, 255)
    magenta = (255, 0, 255)
    yellow = (255, 255, 0)
    orange = (255, 127, 0)
    gray = (100, 100, 100)

    size = (width, height)
    xmax = width - 2
    ymax = height - 1


    try:
    pygame.init()
    window = pygame.display.set_mode(size)
    window.fill(black)
    pygame.display.set_caption('picamera stream on pygame')
    vs = PiVideoStream()
    #dirty but currently the only way so buttons work
    vs.start()
    vs.stop()

    buttons = [
    Button(text='Start', callback=vs.start, _font="droidsans", fontsize=42, pos=(30, 0), screen=window),
    Button(text='Stop', callback=vs.stop, _font="droidsans", fontsize=42, pos=(30, 60), screen=window),
    ]

    for button in buttons:
    button.default_color = white
    button.hover_color = gray
    pygame.display.update(button.draw())

    clock = pygame.time.Clock()
    while True:
    clock.tick(FPS)
    dirtyrects = []
    if vs.running == True:
    dirtyrects.append(update_stream_screen(vs, window))
    for event in pygame.event.get():
    if event.type in (QUIT, KEYDOWN):
    exit()
    elif event.type == pygame.MOUSEBUTTONDOWN:
    for button in buttons:
    if button.rect.collidepoint(pygame.mouse.get_pos()):
    printD(button.text)
    button.action()
    sleep(0.2) #give videostream some time to start...

    if buttonHover:
    for button in buttons:
    if button.rect.collidepoint(pygame.mouse.get_pos()):
    button.hovered = True
    dirtyrects.append(button.draw())
    else:
    button.hovered = False
    dirtyrects.append(button.draw())

    if dirtyrects:
    pygame.display.update(dirtyrects)

    if debugFPS and vs.running == True:
    print('FRAMERATE: %.3f fps' % clock.get_fps())

    except (KeyboardInterrupt, SystemExit):
    print('Quit')
    vs.quit()
    pygame.quit()
    except Exception, error:
    print('Error: ' + str(error))
    exit()
    [/php]

    Soweit scheint alles perfekt zu funktionieren und gefällt mir eigentlich recht gut (abgesehen vom dirty), vor allem der Verzögerungsfreie Stream welcher erstaunlich Ruckelfrei angezeigt wird ;)

    Vielen Dank an alle Helfer!


    pygame picamera lowest streaming latency

  • Warum das so und nicht anders geht erschliesst sich mir auch nicht - da muesste man wahrscheinlich auf C-Ebene irgendwie debuggen, das auch noch mit mehreren Threads - oerks.

    Was ich aber an deiner Stelle machen wuerde: deinen hack *in* die Klasse PiVideoStream ans Ende des __init__ packen. Denn das ist ein Implementierungsdetail, dass ein unbedarfter Beobachter nicht kennen sollte. Denn sonst greift sich jemand deine Klasse & packt sie zB in ein bestehendes Pygame - Projekt - und ploetzlich klappt wieder nix.

    Last but not least: betreibst du SEO mit deinen Schluesselworten am Ende?!?

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!