Ein Toolkit auf SDL rendern

Du hast eine Idee für ein Projekt?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Wie die Überschrift vermuten lässt, würde ich gerne ein bestehendes GUI-Toolkit wie Tk oder gtk auf SDL rendern.
Da ich in pygame jetzt längere Zeit nach einem vernünftigen Toolkit gesucht und bis auf ocempgui(was auch noch nicht ausgereift ist) nichts gefunden habe, frage ich mich nun ob man ein Toolkit für SDL nutzen kann. Zudem ist ocempgui extrem lahm. Pyglet wäre auch eine Option, welches aber auf OpenGL basiert, wo von ich mich gerne noch fernhalten würde.

Jedenfalls nach etwas Suchen habe ich einige Randprojekte gefunden, einen Xlib-Wrapper der im Prinzip Tk vorgaukelt, dass das SDL-Surface ein X11-Surface ist. Funktioniert aber nur für reines Tcl.
Ein anderes wäre gtksdl.

Mein Idee wäre, dass man das auch mit Tkinter hinbekommt. Tkinter ist im Prinzip ja nichts anderes als ein Tcl-Wrapper. Wäre sowas also möglich?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ok, habe mir das mal genauer angesehen und muss gestehen das es leider nicht mal annährend so einfach ist. Obwohl einiges doch recht banal wirkt, wird es spätestens in der Funktionalität bei umfanreicheren Widgets recht komplex.

Aber schreibt sich denn jeder für SDL sein eigenes Toolkit? Gibt es da nicht mittlerweile ein oder zwei Toolkits die dort dominant sind, immerhin ist SDL schon gut 10 Jahre alt!?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also ich denke nicht, dass es da ein besonders hervorstechendes UI-Toolkit gibt! Ich hatte vor einiger Zeit da auch schon mal geguckt, aber nicht viel gefunden, was brauchbar aussah.

OcempGUI kannte ich jedoch noch nicht, danke für den Tipp! Hast Du Dir mal Albow angeguckt? Das sah für mich damals recht gut aus.

Wenn man sich mal in pygame geschriebene Spiele anguckt, sieht man ja schnell, dass da nur wenige eine aufwendige Menüführung und UI-Elemente besitzen. Klar, eine UI ohne brauchbares Toolkit zu entwickeln ist eben eine Qual.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ja, Albow hatte ich mit angesehen, ist aber wie PUG kein wirklich ernstzunehmendes UI-Toolkit und kommt an ocempgui nicht mal Ansatzweise heran. Ocempgui ist schon das beste was ich gefunden habe, vernünftig konzipiert, sauberer Quellcode, gute Dokumentation und auch eines der wenigen Toolkits die Layer- und Eventhandling haben. Nur leider noch nicht ausreichend getestet und so kommt es zu einigen unschönen Fehlern. zB. der "event_grabber" bei mehr als zwei Layern.
Ein anderes Problem hatte ich beim zerstören der Widgets über ein Radiobutton. Ziel war es die Sprache sofort beim anklicken des Radiobuttons zu ändern. Dazu wollte das Menü einfach zerstören und neu aufbauen mit der anderen Sprache(sieht man auch noch in der GitHub-Version vom snake-Spiel, habe ich aber wegen der schlechten Performance geändert). Das Problem stellte sich aber durch das Eventhandling dar. Jedes Klick-Ereignis wird in ocemp in einer Warteschlange hinterlegt, aber eigene Befehle haben einen kürzeren Weg, als fest definiert wie eine "select" Befehl. Das Ergebnis war das "destroy" vor dem "set_active" ausgeführt wurde und "set_active" dann natürlich eine Exception geworfen hatte, weil der Button schon nicht mehr existierte. Zugegeben an dem Fehler bin ich selbst Schuld.

Mein größtes Problem mit diesen GUIs ist aber überall das gleiche, es gibt immer nur absolute Positionierungen. In ocemp gibt es wenigsten schonmal H- u. VFrame, aber auch nur sehr eingeschränkt und man muss selbst dafür sorgen das alles genau ausgerichtet wird, finde ich schon ziemlich nervig.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Xynon1 hat geschrieben:Ok, habe mir das mal genauer angesehen und muss gestehen das es leider nicht mal annährend so einfach ist. Obwohl einiges doch recht banal wirkt, wird es spätestens in der Funktionalität bei umfanreicheren Widgets recht komplex.
Also ich denke in GTK+ würde es reichen Gdk mit einem SDL-Backend zu versehen und der Rest wäre geschenkt.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
deets

Warum willst du dich von OpenGL fernhalten? In der Inkarnation von pyglet ist das doch nur ein rendering-backend - das heisst nicht, dass du 3D-Kram machen musst.

Und es koennte die Antwort auf deine Probleme sein, da alle grossen Toolkits OpenGL rendering-Kontexte haben, und pyglet darauf zu adaptieren sollte sehr simpel sein.

Darueber hinaus sehe ich das deutlich groessere Problem beim Event-Loop. Den von Pygame nach Toolkit-der-Wahl zu wuchten ist sicher alles andere als trivial (und ohne C nicht moeglich).
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@Leonidas
Hättest du dazu auch gleich noch passende Lektüre wie man das mal ebend macht :D
Nur ein Scherz, gefunden habe ich auch schon so einiges auf der http://www.libsdl.org/cgi/docwiki.cgi/FAQ_GUI.
Mit Tkinter habe ich ich jetzt auch schon geschafft ein Widget auf SDL zu rendern, nur müsste ich alles andere auch Nachziehen und Support würde gar nicht gehen, weil ich sowohl in der _tkinter.c als auch in der halben Tk-Lib einige Einträge ändern musst. (War auf jedenfall recht interessant)
GTK stand als nächstes auf der Liste und damit sieht das ganze auch besser aus.

@deets
Ich wollte mich nicht direkt von dem "3D-Kram" fernhalten, sondern von der Abhängigkeit zu OpenGL. Aber eventuell komme ich nicht drumherum.
Was die Event-Loops angeht, soll es ja im oben genannten Link unter "Double event loop issue" einigermaßen funktionieren(mal sehen). War aber bei ocemp auch ein Problem, bzw. hatte ich die Möglichkeit nicht gefunden und es erst später heraus bekommen, das man die "pygame.Event"s direkt an ocemp's-Eventhandler übergeben kann. Deshalb ist mein Programm zur Zeit vom Menü strikt getrennt.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@Leonidas
Jetzt muss ich doch noch mal fragen, wie man das am besten angeht. Ich habe es jetzt Spaßeshalber mal umgedreht gemacht, so dass ich ein SDL-Fenster in einem gtk.Window habe, allerdings müsste ich hier natürlich alle Events von pygtk zu pygame durchreichen. Aber andersherum bekomme ich es nicht hin, da habe ich immer nur ein schwarzen Bildschirm. Könntest du mir also einen Hinweis geben wie du das angehen würdest?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Puh, aus dem Stehgreif wüsst ich da nichts. Ich würde halt bei Problemen die GDK/GTK-Entwickler fragen, wie man da bestimmte Sachen machen kann.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ok, ich lasse das an der Stelle wohl besser sein und bleib bei ocempgui. Dann ist das Menü halt etwas langsam.
Wen es noch interessiert, um die Events von pygtk zu pygame durchzureichen, kann man sugargame nutzen, ist mir aber an der Stelle zu aufwendig.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich habe immer noch Schwierigkeiten mich an die GUI-Toolkits von Pygame zu gewöhnen bzw. kann mich mit diesen nicht anfreunden. Ich habe jetzt zwar ein "gutes" GUI-Toolkit für SDL gefunden, nur ist dieses in C++ geschrieben. Meine Frage wäre nun, sollte man sich einen Wrapper basteln oder gleich das komplett Kit nachbauen?
Das Toolkit welches ich gerne nutzen würde heißt "guichan" und es wurde schon einmal angefangen einen Wrapper für Python zu bauen. Dieser heißt "PyChan" welcher aber, soweit ich das beurteilen kann, tot ist und die einzige Version 0.0.1 von 2008 macht da auch nicht große Hoffnung, das es dort weiter geht. Und es ist auch nicht einfach portierbar.
Ein Grund wieso ich überlege "guichan" nachzubauen ist die Vererbungs-"Strategie", denn dort wird fast ausschließlich Mehrfachvererbung verwendet und diese würde ich gerne gleich mit verbessern wollen. Also halt ein eigenes Toolkit welches an "guichan" angelehnt und auf pygame basieren würde.

Bevor jetzt jemand sagt und meint das wäre sehr/zu Aufwendig, dessen bin ich mir bewusst. Selbst bei einem so "kleinen" Toolkit wird es ein riesen Aufwand um es neu in einer anderen Programmiersprache zu entwerfen.
Was haltet ihr von der Idee? - oder sollte ich besser die Klappe halten und mich endlich mit einem bestehenden Toolkit anfreunden? :mrgreen:
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Habe mir mal eine Übersicht verschafft was ich alles bräuchte:
Bild
Direkter Link Edit: Bild ausgetauscht.

Der obere weiße Teil ist das, was von dem Toolkit bereitgestellt werden sollte und das graue Kästchen unten, ist das was ich mit dem Toolkit bauen möchte.

Das Event-Handling würde ich wie in "guichan" machen, also die Listner oben in der Grafik würden die verschiedenen "pygame.event.Event"s für die Widgets bereitstellen. So das in jeder Updatephase die Widgets prüfen ob für sie etwas zutun ist.

Ich hoffe wenigstens auf ein paar Kommentare dazu :D
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich habe mich noch nie mit pygame oder SDL oder dem erwähnten GUI-Toolkit beschäftigt, aber als Java noch jung und Swing nur eine Idee war, habe ich dafür mal ein GUI-Toolkit gebaut und da ich ein bisschen die Geschichte des GUI kenne, erlaube ich mir einfach mal ein bisschen OT-Rambling...

...denn alles, was man (theoretisch) braucht, ist bitblt. Diese legendäre Funktion war die Grundlage der ersten Smalltalk-Bitmap-Grafik-GUIs und so wichtig, dass Amiga und später auch Atari diese in Hardware nachgebaut haben. Bitblt(srcbm, srcx, srcy, dstdm, dstx, dsty, width, height, mode) kann einen Bitmap-Ausschnitt in einen anderen kopieren. Dazu gibt es verschiedene Modes, z.B. einfach nur kopieren, dabei invertieren, nur löschen, nur setzen oder ein paar andere Kombinationen, die nicht so sinnvoll sind. Daher kann Bitblt() nicht nur kopieren oder Bildschirmausschnitte scrollen (denn srcbm und dstdm können gleich sein), sondern auch Bereiche highlighten (etwa ein gedrückter Button oder ein ausgewählter Menüeintrag), löschen oder einfärben (bei Schwarzweiß-Bildschirmen ist das einfach). Kann man Rechtecke malen, kann man damit auch einfache Linien ziehen und dank Bresenham auch Linien mit beliebiger Steigung. Schließlich kann man auch Kreise oder Kreissegmente malen - und alles ohne Multiplikation (jedenfalls die Linien) oder Division mit einfachen Operationen. Selbst Textdarstellung ist nicht schwer, wenn man eine Offscreen-Bitmap mit allen Glyphen hat und dann Zeichen für Zeichen kopiert.

Smalltalk kam 1976 mit scrollbaren Listen, mehrzeiliger scrollbarer Texteingabe, Buttons und Kontextmenüs (auch scrollbar) aus. Diese konnte man zu Panes und diese zu Windows kombinieren, die auf einem Screen angezeigt wurden. Die mehrzeilige Texteingabe ist ekelig, aber ansonsten ist das nicht wirklich schwer zu implementieren.

Wesentlich mehr konnte der Amiga 10 Jahre später auch nicht, allerdings gab es eine sehr komplexe Bibliothek names layers, die überlappende Fenster mit unterstützte, wo man auch dank komplexer Clipping-Rectangle-Listen in die teilweise überdeckten Fenster schreiben konnte.

All die frühen GUI-Systeme gehen davon aus, dass man einen Screen mit einer Bitmap hat, die ihren Inhalt bewahrt - genau wie der HTML-Canvas.

Spiele, und damit sicherlich auch pygame und/oder SDL, zeichnen üblicherweise X mal pro Sekunde einen Bildschirm komplett "from scratch" neu und stellen während dessen eine zweite Bitmap dar, um danach umzuschalten und dann die nächste Offscreen-Bitmap zu füllen. Damit das nicht all zu ineffizient ist, kann man sich Teile in weiteren Bitmaps merken, die man dann nur noch kombinieren muss. Zudem ermöglicht dies Semi-Transparenz von Objekten.

Eigentlich sind GUIs immer event-gesteuert, was sich mit dem Polling-Ansatz von Spielen ein bisschen beißt. Netterweise nutzte aber auch das Ur-GUI von Smalltalk Polling-Controller und so funktioniert das sogar besser für einen "game loop" als all der moderne Kram mit Events, Damage Rectangles und Callbacks für Redraws.

Grundkomponenten für eine GUI sind Views, die wieder Views enthalten können. Man braucht etwas, das eine Grafik oder einen Text darstellen kann. Nennen wir das ImageView und TextView. Man braucht etwas Anklickbares, nennen wir das ButtonView. Kombiniere ich ein Label und einen Button, kann ich ihn beschriften. Ordne ich mehrere Buttons untereinander an, habe ich ein Menü. Ich brauche nun etwas, das einen View enthält, aber nur einen Ausschnitt davon anzeigt und daneben einen Indikator, wie der Ausschnitt zum Ganzen in Beziehung steht. Das nenne ich einen ScrollView. Den Rollbalken könnte ich theoretisch auch wieder aus Views zusammensetzen und auch getrennt benutzen, aber das wollen wir mal ignorieren. Mit dem Scrollview und einem View, in dem ich viele Labels darstelle, habe ich eine Liste. Wahrscheinlich muss ich noch ein bisschen mehr machen, damit ich eine Selektion verwalten kann, aber das Prinzip bleibt einfach. Viel mehr brauche ich eigentlich nicht.

Jeder View muss auf die Maus (oder Berührungen wenn wir an Tablets denken) reagieren können. Natürlich auch auf Tastatureingaben.

Hatte ich schon erwähnt, dass Texteingabe recht aufwendig ist? Ich brauche eine Grundoperation, die mir die Länge eines Teiltextes in Pixel berechnen kann. In der guten alten Zeit konnte man einfach die Anzahl der Zeichen zählen und mit der Zeichenbreite multiplizieren. Oder bei Proportionalzeichen jede Zeichenbreite nachschlagen und dann aufaddieren. Und vielleicht noch Kerning-Paare berücksichtigen. Heutzutage gibt es aber noch BIDI und Ligaturen. Daher gibt es hoffentlich einen Systembefehl, der mir die Breite direkt liefern kann. Dann muss ich den Start und das Ende einer Selektion (das Modus-freie Bearbeiten von Text mit Hilfe einer Selektion die auf cut/copy/paste reagiert und wo ein neues Zeichen die Selektion überschreibt ist übrigens auch eine Erfindung von Smalltalk) verwalten und jetzt erst den Textteil vor der Selektion umbrechen und darstellen, dann den in der Selektion und dann den nach der Selektion, jeweils mit anderem Hintergrund und ggf. anderer Vordergrundfarbe. Habe ich keine Selektion, muss ich einen blinkenden Cursor darstellen. Dieser blinkt aber nur, wenn ich nicht tippe, schon mal aufgefallen? Den ganzen Krempel kann ich jetzt noch scrollen. Ohne weitere Optimierungen ist das wahrscheinlich zu langsam. Daher muss ich von meinem reinen Modell, das alles Views sind, abweichen, und Views wie der für den Text wissen, wenn sie in einem ScrollView stecken, dass sie gar nicht alles darstellen müssen, sondern nur den sichtbaren Abschnitt.

Wie auch immer, ein wie ich finde spannendes Thema. Daher: Baue dir dein eigenes GUI-Rahmenwerk. Dabei kann man viel lernen :)

Stefan
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Danke, für deine ausfürliche und auch aufmunternde Anwort :D.

Mir wird den von dir beschriebenen Schwerpunkten auch schon viel abgenommen, beispielweise die blit-Methode, ist in SDL(pygame) Standard. Und über die meisten der Punkte habe ich mir auch schon gedanken gemacht und für den Bau der Widgets würde ich zwar Bilder nehmen, aber nicht für die Verwaltung. Wie oben aus der Grafik hervorgeht würde ich von pygame.Rect ableiten, diese Klasse bietet ein vollständiges Rechteck mit allen Positionsdaten, damit muss ich mich darum schon nicht mehr kümmern. Dann könnte man einem Widget immer ein Bild zuordnen und ein "pygame.sprite.Sprite" like Object erzeugen, damit wäre dann auch schon die Gruppierung und das Überlappen von Fenstern gelöst. Ich würde den Widgets jedem noch eine Draw-Methode dazu geben, damit sie selbst bestimmen können wie sie gezeichnet werden, jedes sollte hierbei Konturen mit zeichnen können, damit man sie auch ohne Bild erstellen kann. Dazu gibt es in pygame auch schon genug im "draw"-Modul. Das "gfx-draw"-Modul ist in pygame leider noch im experimentier Status was ich dann später eventuell ergänzen kann, da es um einiges schneller als das "draw"-Modul ist.
Was bereiche "highlighten" angeht habe ich mir auch schon was überlegt, bzw. gefällt mir das Prizip von "guichan" und zwar einen festen Grauwert der entweder von der Zeichenfarbe ab und dazu gerechnet wird und das ganze dann überlagert "blitten".
Texterstellung wird mit pygame auch nicht besonders schwer, allerdings müsste man ein wenig mehr Funktionen für die Positionierung haben, deswegen auch die Ableitung in der Grafik. Was die Offscreen-Bitmap angeht, so habe ich schon in Blender damit gearbeitet, wäre als kein Problem. Allerdings halte ich sie hier für unnötig da ein Font in gewisser Weise auch nichts anderes ist und pygame diese Problemlos nutzen kann.
Die von dir angesprochenen Layers stellen in pygame beim zeichnen auch mit Transparenz kein Problem dar. Die Schwierigkeit hier sehe ich in der Focus-Verwaltung, wann wie und wo muss der Focus wechseln, etc.. Dies ist aber in "guichan" schon relativ gut einsehbar und ich werde mich erstmal daran orientieren.
Scrollbars sind leicht zubauen, aber auch hier ist die Funktion nicht ganz einfach, da ich hier mehr oder weniger 3 Rechtecke habe, einmal das Fenster und dann der Inhalt und der zu zeichnende Bereich in der Größe des Fensters und auf einer bestimmten Position des Inhaltes. Aber das bekomm ich auch noch hin :wink:

Die Aktualisierung, üblicherweise wird der Screen einmal pro Frame(deswegen ja auch der Name) einmal das Bildkomplett gezeichnet. Man kann dies einschränken und nur einzelne Rechtecke erneuern lassen, da muss ich erstmal sehen, wie die Performance aussieht. Letzendlich sollte immer nur das komplette Widget(die unten im grauen Kästchen) als ein Bild vorliegen, wird etwas daran geändert wird die "redraw"-Variable des Widgets gesetzt und bei dem nächten Aufruf der Draw-Methode wird das Widget-Bild neu erzeugt. Damit ist eine relativ hohe Performance möglich. Das habe ich auch schon bei Tileset-Maps erprobt.
Das Eventsystem bereitet mir immer noch das größte Kopfzerbrechen, wie ich schon geschildert hatte würde ich die Widgets gerne selbst verwalten lassen, so das sie halt einmal in der gameloop durchlaufen werden und die Events prüfen, das könnte aber IMHO zu Performance Problemen bei einer großen Anzahl von Widgets führen. Hier liefert mir auch "guichan" nicht den passenden Hinweis, da es leider das Problem über die Listnener der einzelnen Widgets liefert, in dem die Widgets sich selbst wie ein EventLogger verhalten(sprich davon abgeleitet sind). Ich müsste also die "pygame.event.Event"s entsprechen den Widgets zuordnen können, wo wieder das Fokussystem eine Rolle spielen würde und immer nur das fokusierte Widget Events bearbeitet.

Das mit den Grundkomponenten habe ich ja schon in der Grafik gezeigt, wie man das bauen kann. Das man den Button aber nochmal vom Label ableitet ist eine gute Idee, dann muss ich mich nicht zweimal um die Positionierung des Textes kümmern.

Maus und Tastur wären ja bei dem Eventhandling mit inbegriffen, wenn nicht sogar die Hauptsache, ob das ganze auf Tablets funktioniert kann ich nicht sagen, ich habe leider keines. Könntest es ja mal für mich ausprobieren ob pygame mit Tablets klar kommt, spiele wo mit man das testen könnte gibt es ja auf pygame.org mehr als genug.

Texteingabe ist aufwendig aber nicht schwierig, wie ich oben schon erläutert hatte gibt es in pygame bereits eine solche Funktion und hier könnte ich auch 1:1 auf "guichan" zurückgreifen da das nun wirklich reine Funktionen zur Berechnung sind. Zudem könnte man auch einfach einen Monospace-Font :mrgreen: nehmen, bzw. eine Reihe von diesen. Würde dann halt nicht schön ausehen.
Auch bei den anderen Funktionen einer TextBox habe ich hier sowohl Erfahrung als auch die Kenntnis der interna von Tk und die von "guichan". Da ich nicht einen überdimensionierten Editor basteln möchte, bin ich hier in beidenfällen bei den richtigen Bibliotheken um eine schlichte Textbox zu bauen. Selection, scrollen und cut/copy/paste wären hier wohl am umfangreichsten, wegen den Maus-Dragged-Events und der mehrfachen Steuerung über die Kontrollertasten. Scrollview hatte ich oben ja schon beschrieben wie man das umsetzen könnte.

Und ja, es ist mir aufgefallen das der Cusor nur blinkt wenn er still steht. Vermutlich fängt die Animation immer von vorne an, so das man ihm nie die Zeit bleibt sich mit der Hintergrundfarbe zu zeichnen. Wäre jetzt aber kein muss für die TextBox.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Pygame sucks unter OS/X. Big times. Das vorkompilierte Binary will ich mir nicht installieren, da man das Zeug nie wieder aus dem System bekommt und ich zudem auch gar nicht das System-Python benutze. Schon gar keines, was ich von python.org als Binary installiert habe. Homebrew kennt kein Rezept für pygame und Macports beginnt aufgrund der Abhängigkeit zu numpy erst mal GCC und Fortran und allen möglichen X11-Scheiß zu installieren. Da ging es schneller, Ubuntu in einer VM zu starten...

Ich habe noch nie etwas mit pygame gemacht und auch nicht wirklich die Dokumentation gelesen doch das hier öffnet ein schwarzes Fenster und ist der Startpunkt für mein Experiment:

Code: Alles auswählen

    import pygame

    pygame.init()

    screen = pygame.display.set_mode((400, 400))
    
    while True:
        event = pygame.event.poll()
        if event.type == pygame.QUIT:
            break
    
        screen.fill((0, 0, 0))
    
        pygame.display.flip()
Den Event-Kram scheine ich zu brauchen, um das Fenster per Klick wieder schließen zu können. Als Hintergrundfarbe für mein Fenster habe ich ein freundliches Schwarz gewählt. Mehr als den/die/das Offscreen-Surface-Objekt "screen" immer wieder zu füllen und anzuzeigen mache ich nicht. Bin trotzdem stolz drauf :)

Nun kann ich meine GUI-Bibliothek in einer Datei "ui.py" schreiben:

Code: Alles auswählen

    import pygame

    class View:
        def __init__(self, rect, color=(240, 120, 0)):
            self.rect = rect
            self.color = color
            self.subviews = []
            self.superview = None

        def add(self, view):
            self.subviews.append(view)
            view.superview = self

        def remove(self):
            self.superview.subviews.remove(self)
            self.superview = None
    
        def do_draw(self, surface):
            self.draw(surface)
            for view in self.subviews:
                view.do_draw(surface)
        
        def draw(self, surface):
            pygame.draw.rect(surface, self.color, self.rect)
Fertig. Ein View hat eine Position und Größe, kann weitere Views als Kinder haben und kann sich selbst als Rechteck darstellen. Nicht viel, aber ein Anfang für ein GUI-Rahmenwerk. Mehr als ein Kompositum-Entwurfsmuster steckt da nicht hinter.

So kann ich's einbauen:

Code: Alles auswählen

    import pygame, ui

    pygame.init()

    screen = pygame.display.set_mode((400, 400))

    view = ui.View(pygame.Rect(10, 20, 30, 40))
    view.add(ui.View(pygame.Rect(20, 20, 20, 10), (240, 0, 120)))
    
    while True:
        event = pygame.event.poll()
        if event.type == pygame.QUIT:
            break
    
        screen.fill((0, 0, 0))
        
        view.do_draw(screen)
    
        pygame.display.flip()
Der nächste Schritt wären jetzt Layouts, mit denen ein View seine Kinder automatisch anordnen kann und die jedem View eine optimale Größe geben können. Layout möchte ich zudem nur machen, wenn es notwendig ist, d.h. ich muss schauen, wann mein rect geändert wird. Dann könnte ich auch das Zeichnen optimieren und zu jedem View ein surface-Objekt als Cache verwalten. Dazu müssen dann set_need_draw()-Aufforderungen durch die View-Hierarchie wandern. Braucht man aber vielleicht alles nicht, weil auch so schnell genug. Also will ich euch damit nicht langweilen. Und mein Lieblingslayout, welches flexible Grids benutzt, ist auch nicht so einfach zu schreiben.

Ich zeige lieber noch ein Label als weitere UI-Komponente:

Code: Alles auswählen

    class Label(View):
        def __init__(self, rect, text="", color=(255, 255, 255)):
            View.__init__(self, rect, color)
            self.text = text
            self.font = pygame.font.SysFont("liberationsans", 16)
        
        def draw(self, surface):
            surface.blit(self.font.render(self.text, True, self.color), self.rect)
Ohne Layout und Größenbestimmung ist das allerdings nur der halbe Spaß. Ich müsste eigentlich schauen, wie groß der Text ist und dann diesen innerhalb des Rechtecks zentrieren oder irgendwie anders anordnen. Mehrzeiligen Text kann pygame wohl gar nicht, da müsste man sowieso viel rechnen. Auch für unterschiedliche Schriften, die man am besten alle an der Baseline ausrichtet, muss man noch mal nachdenken. Ich weiß auch nicht, was pygame bei unicode statt str macht... (Die Qualität der Schriftdarstellung ist übrigens trotz Antialiasing nicht so doll. Da bin ich besseres von OS X gewöhnt. Wie schade, denn damit steht und fällt viel bei einem UI-Rahmenwerk)

Für einen Button muss ich Mausklicks verarbeiten können. Dies ist meine 3-min-Lösung:

Code: Alles auswählen

    ...
    event = pygame.event.poll()
        if event.type == pygame.QUIT:
            break
        if event.type == pygame.MOUSEBUTTONDOWN:
            active_view = view.subview_at(event.pos)
            if active_view:
                active_view.mouse_down(event.pos)
        if event.type == pygame.MOUSEBUTTONUP:
            if active_view:
                active_view.mouse_up(event.pos)
Wie man sieht, suche ich bei einem Klick den angeklickten View und wenn ich einen finde, sage ich diesem, dass er angeklickt wurde. Wird die Maus wieder losgelassen, suche ich NICHT den passenden View, sondern sage dies dem alten. Das ist das übliche Mouse Capture-Verhalten, was zwar nicht für D&D (nicht das Rollenspiel sondern Drag 'n' Drop) passt, aber den Button schön einfach macht.

Dann kann ich die Klasse View wie folgt erweitern:

Code: Alles auswählen

    def subview_at(self, pos):
        for view in self.subviews:
            subview = view.subview_at(pos)
            if subview:
                return subview
        if self.rect.collidepoint(pos):
            return self

    def mouse_down(self, pos): pass
    def mouse_up(self, pos): pass
Nun ist ein Button mit dem typischen Verhalten, dass man ihn drücken kann und das er eine Aktion auslöst, wenn man die Maus über ihm wieder loslässt, nicht weiter schwer:

Code: Alles auswählen

    class Button(View):
        def __init__(self, rect, color=(120, 120, 240)):
            View.__init__(self, rect, color)
            self.pressed = False

        def mouse_down(self, pos):
            self.pressed = True

        def mouse_up(self, pos):
            self.pressed = False
            if self.rect.collidepoint(pos):
                print "click"

        def draw(self, surface):
            View.draw(self, surface)
            if self.pressed:
                pygame.draw.rect(surface, (250, 250, 0), self.rect, 2)
Ich habe mich dafür entschieden, das Drücken mit einem gelben Rahmen zu visualisieren. Und die Aktion ist einfach ein "print". Das Prinzip sollte aber klar sein. Schön wäre, wenn man bei jeder Mausbewegung prüft, ob sie noch über dem Button ist und da dann noch einen weiteren Status verwaltet (mouse enter und mouse exit), doch das überlasse ich euch.

Wer einen Button mit Text haben will, soll ein Label hinzufügen. Nicht effizient, aber generisch und damit per Definition gut.

Nun bleiben eigentlich nur noch zwei Dinge, die etwas schwerer sind: Ein mehrzeiliges Texteingabefeld und ein ScrollView. Damit und einem passenden Layout kann man dann auch Listen und Tabellen bauen. Einen ScrollView bleibe ich euch schuldig, aber ein paar Zeichen eingeben, wie schwer kann das schon sein.

Trial und error zeigt, ich brauche zwei Argumente für eine key_down-Methode, über die ich mir einen Tastendruck schicke: eines mit dem Key-Code, an dem ich z.B. Cursorbewegungen erkenne und eines mit dem Zeichen, das eingegeben wurde:

Code: Alles auswählen

    class TextEdit(Label):
        def __init__(self, rect, text=""):
            Label.__init__(self, rect, text)
            self.cursor = 0

        def key_down(self, key, ch):
            if key == 275 and self.cursor < len(self.text):
                self.cursor += 1
            elif key == 276 and self.cursor > 0:
                self.cursor -= 1
            elif key == 8 and self.cursor > 0:
                self.cursor -= 1
                self.text = self.text[:self.cursor] + self.text[self.cursor + 1:]
            elif key >= 32 and ch:
                self.text = self.text[:self.cursor] + ch + self.text[self.cursor:]
                self.cursor += 1

        def draw(self, surface):
            Label.draw(self, surface)
            w, h = self.font.size(self.text[:self.cursor])
            pygame.draw.rect(surface, (240, 40, 40), pygame.Rect(self.rect.left + w, self.rect.top, 2, h))
Ich stelle einen roten Cursor über dem durch die von Label geerbte Methode gezeichneten String dar. Blinkt zwar nicht, ist aber so sehr einfach. Eigentlich müsste ich auch nicht 30 mal pro Sekunde (oder wie häufig pygame das flip() macht) die Größe bestimmen, sondern muss das nur machen, wenn der Benutzer mal ein Zeichen eingibt, doch optimieren wollte ich nicht.

Daher sage ich auch einfach, den (unsichtbaren) Fokus hat immer die angeklickte Komponente, d.h. ich kann meine Hauptschleife wie folgt erweitern:

Code: Alles auswählen

    if event.type == pygame.KEYDOWN:
            if active_view:
                active_view.key_down(event.key, event.unicode)
Und fertig ist mein Texteingabefeld, in dem ich den Cursor bewegen kann (nachdem ich es einmal angeklickt habe), wo ich mit Backspace (nicht aber DEL) ein Zeichen löschen und ansonsten beliebige Zeichen eingeben kann. Leider funktioniert kein Tastatur-Repeat. Ach, und es wäre natürlich schön, wenn ich auch noch mit der Maus den Cursor setzen könnte.

Jetzt vielleicht nur eine Selektion? Und bitte mehr als eine Zeile. Und natürlich Wortumbruch. Und Cut/Copy/Paste. Dafür dann bitte ein Kontextmenü. Und dann einen blinkenden Cursor.

Allgemein Animationen wären nett. Wenn ich die Farbe eines Buttons ändere, weil er angeklickt wurde, soll das bitte nicht sofort passieren, sondern innerhalb von 0,5 Sekunden. Und wenn die Animation noch nicht abgeschlossen ist und ich nochmals die Farbe ändere, muss ich die erste abbrechen können und es darf zu keinem Farbsprung kommen. So schwer kann das eigentlich auch alles nicht sein. Ich vermute, ein guter Ansatz ist wie der von Apples UIKit, wo die UIView-Hierarchie noch einmal intern komplett als CALayer-Hierarchie gedoppelt wird, die sich dann um alle Animationen kümmert.

Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Xynon1, ohne wirklich pygame studiert zu haben sieht es wirklich so aus, dass da schon Support für diverse Grundoperationen vorhanden ist. Von Rect ableiten würde ich dennoch nicht. Vererbung ist in der Regel kein gutes Design, besser ist eine Komposition, siehe mein Beispiel, wo Views ein Rect haben, aber kein Rect sind. Gleiches gilt für Button, den du nicht von Label ableiten willst, sondern der bestenfalls ein Label enthält. Was, wenn du eine Grafik im Button darstellen willst? Eine zweite Button-Unterklasse? Keine gute Idee. Zu Sprites kann ich nichts sagen, aber wenn du eh X mal pro Sekunde das UI darstellst, ist Überlappung kein Problem, es wird ja in der richtigen (immer gleichen) Reihenfolge alles dargestellt.

Focusverwaltung ist IMHO einfach, siehe meine "active_view"-Variable für eine absolute Minimallösung. Statt ein und die selbe Variable für Maus und Tastatur zu benutzen, sollten das aber zwei sein und dann kann jede Komponente fragen, ob sie fokussiert ist und sich anders darstellen. Z.B. hat nur das fokussierte Texteingabefeld einen Cursor. Nicht fokussierte Eingabefelder zeigen zudem meist eine Selektion in einer anderen Farbe an, als wenn sie fokussiert wären. Andere Komponenten wie Buttons haben meist noch eine Strichellinie.

Stefan
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Hui, danke für die Mühe und die interessanten Ausführungen. Ich werde das ganze wohl noch einmal gründlich lesen müssen, aber ein paar Anmerkungen hätte ich dennoch schon.

1. Wieso sollte man zum Beispiel nicht von "Rect" erben? Dies würde mir besonders die Positionierung wesentlich erleichtern. In deiner View müsste ich sagen "view.rect.center = 45", wieso also nicht gleich "view.center = 45". Positionen und Größen sind doch eindeutige Eigenschaften eines Widgets. Was spricht da so gegen Vererbung?
Bei dem Button stimme ich dir allerdings voll und ganz zu, ich hatte jetzt hin und her überlegt wie man die Strucktur von den verschieden Buttons(und dem Label) möglichst einfach halten kann und bin auch nur zu diesem Schluss gekommen.

2. Maus Enter/Exit wäre IMHO ein Fokus und sollte leicht mit einer Methode ermöglicht werden können. Werde ich mir auch gleichmal näher anschauen.

3. Wieso das beim TextEdit wirklich mit *echten* Strings gestalten? Sollte es nicht effektiver sein den Text als Liste vorzuhalten und immer beim "redraw" mit ".join" diesen zuverbinden, als ihn bei jedem zeichnen zu zerstückeln und zu addieren. Allerdings müsste ich das erstmal ausprobieren. (Warscheinlich ist deines schneller - so wie ich mich mit meinen Vermutungen kenne)

Ich habe heute auch schon angefangen und würde mal behaupten ich bin schon ein gutes Stück weiter. Du hast das warscheinlich auch nur mal schnell nebenbei geschrieben.:mrgreen: Ich werden so schnell es mir möglich ist meine jetzige Version hochladen. Es würde mich freuen wenn du die Zeit findest dann mal drüber zuschauen.
btw. ich habe immer noch keinen Namen wie ich das Toolkit nennen könnte.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Xynon1: Vererbung stellt normalerweise eine Ist-Ein-Beziehung dar. Und ein View ist kein verändertes Rect, sondern ein View nutzt das Rect als Hilfsmittel zur Darstellung. Auch wenn es dir wie unnötige Tipparbeit vorkommt, so finde ich es durchaus sauberer, die besagte Trennung zu vollziehen. Guck dir mal an, wie andere Frameworks das machen, z.B. in Qt das QRect, welches an die Basisklasse QWidget gehangen wird.

Du solltest halt im Hinterkopf behalten, dass so ein View, wie es beispielhaft von sma gezeigt wurde, noch etwas mehr tut, als sich bloß um die Positionierung zu kümmern. Es vereint sozusagen mehrere Einzelaufgaben miteinander. Im Beispiel springt einem ja `self.color` direkt ins Auge. Wenn du da zuviel miteinander vermischt und wenn dies auch noch von den eigentlichen Widgets als Basis genutzt werden soll, dann verliert man irgendwann schnell den Überblick. Zudem verschwimmt damit die Einteilung in Aufgabenbereiche, die für Klassen eigentlich üblich ist.

Übrigens lässt sich die Problematik ja relativ gut abstrahieren, ohne dass man bloße Forwarding-Methoden, wie `center()`, zur Positionierung einbauen muss. Ein Label etwa könnte mit dem Attribut `alignment` erstellt werden. Für die eigentlich Positionierung der Widgets könnte man Layout-Manager zur Verfügung stellen. Das ist ohnehin sauberer.

Mir ist natürlich klar, dass man ein Spiel-UI nicht direkt mit einem "normalen" GUI-Framework vergleichen kann, ich denke aber trotzdem, dass man durchaus auf bewährte Methoden zurückgreifen sollte. Zum Einen haben diese sich als praktikabel erwiesen und zum Anderen sind Programmierer, die schon mal etwas mit grafischen Oberflächen zu tun hatten, mit den entsprechenden "Gepflogenheiten" vertraut.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich hatte Lust noch einen `ScrollView` zu bauen. Zuerst führe ich noch ein `Layout`-Objekt ein, welches die optimale Größe eines Views unter Berücksichtigung der Größen der Kinder bestimmen kann (`best_size`) und alle Kinder anordnen kann (`do_layout`):

Code: Alles auswählen

    class Layout:
        def best_size(self, view):
            return view.rect.size
        
        def do_layout(self, view):
            pass

    class VBox(Layout):
        def best_size(self, view):
            width, height = 0, 0
            for subview in view.subviews:
                w, h = subview.best_size()
                width = max(width, w)
                height += h
            return width, height
        
        def do_layout(self, view):
            top = view.rect.top
            width, height = view.rect.size
            for subview in view.subviews:
                _, h = subview.best_size()
                subview.set_rect(Rect(view.rect.left, top, view.rect.width, h))
                subview.do_layout()
                top += h
Nun kann ich meinen `View` ändern, damit er ein `Layout` benutzt:

Code: Alles auswählen

    class View:
        def __init__(self, ...):
            self.layout = Layout()
            self.need_layout = False
        
        def add(self, view):
            self.need_layout = True
            self.subviews.append(view)
            view.superview = self
        
        def remove(self):
            self.superview.need_layout = True
            self.superview.subviews.remove(self)
            self.superview = None
        
        def best_size(self):
            return self.layout.best_size(self)
        
        def do_layout(self):
            if self.need_layout:
                self.layout.do_layout(self)
                self.need_layout = False
        
        def set_rect(self, rect):
            if self.rect != rect:
                self.set_pos(rect.topleft)
                self.set_size(rect.size)
        
        def set_pos(self, pos):
            if self.rect.topleft != pos:
                self.move(pos[0] - self.rect.left, pos[1] - self.rect.top)
        
        def move(self, dx, dy):
            self.rect = self.rect.move(dx, dy)
            for view in self.subviews:
                view.move(dx, dy)
        
        def set_size(self, size):
            if self.rect.size != size:
                self.rect = Rect(self.rect.topleft, size)
                self.need_layout = True
        
        def pack(self):
            self.set_size(self.best_size())
            self.do_layout()
Die Methode `best_size` delegiert die Arbeit nun an das Layout-Objekt. In `do_layout` tue ich nur etwas, wenn auch ein Layout notwendig ist - und das ist es nur, wenn ich Kind-Views hinzufüge oder entferne oder die Größe ändere, was ich in `set_rect` prüfe und in `set_size` mache. Ich muss noch den Fall berücksichtigen, dass ich den View verschiebe. In diesem Fall muss ich auch alle Kinder verschieben, da ich nur ein gemeinsames Koordinatensystem habe. Meist haben UI-Rahmenwerke lokale Koordinaten, d.h. Kinder sind immer relativ zu ihren Eltern positioniert. Vielleicht sollte ich subsurfaces benutzen, damit wäre es vielleicht einfacher.

Für einen `Label` kann ich nun `best_size` überschreiben:

Code: Alles auswählen

    class Label:
        def best_size(self):
            return self.font.size(self.text)
Nun kann ich eine Liste von Labels erzeugen und automatisch anordnen:

Code: Alles auswählen

    labels = ui.View(pygame.Rect(0, 0, 0, 0), (50, 100, 200))
    labels.layout = ui.VBox()
    for i in range(10):
        labels.add(ui.Label(pygame.Rect(0, 0, 0, 0), "Zeile %d" % i))
    view.add(ui.ScrollView(pygame.Rect(200, 5, 70, 90), labels))
Ein `ScrollView` kann nun so einen View als Kind bekommen, darf aber nicht alles darstellen, sondern nur den Ausschnitt, der seinem Rect entspricht. Darüber mal ich dann noch einen Scroll-Indicator. Daher überschreibe ich `do_draw`.

Code: Alles auswählen

    class ScrollView(View):
        def __init__(self, rect, content):
            View.__init__(self, rect, (192, 192, 192))
            content.set_pos(rect.topleft)
            content.pack()
            self.add(content)
        
        def do_draw(self, surface):
            surface.set_clip(self.rect)
            self.subviews[0].do_draw(surface)
            self.draw(surface)
            surface.set_clip(None)
        
        def draw(self, surface):
            content = self.subviews[0]
            bh = self.rect.height - 6
            ch = content.rect.height
            th = self.rect.height * bh / ch
            ty = min((self.rect.top - content.rect.top) * bh / ch, bh - th)
            bar = Rect(self.rect.left + self.rect.width - 6, self.rect.top + 3, 3, bh)
            pygame.draw.rect(surface, (128, 128, 128), bar)
            thumb = Rect(bar.left, bar.top + ty, 3, th)
            pygame.draw.rect(surface, (224, 224, 224), thumb)
Was jetzt noch fehlt ist die Möglichkeit zum Scrollen. Ich reagiere einfach mal auf Cursorbewegungen:

Code: Alles auswählen

    def key_down(self, key, ch):
        content = self.subviews[0]
        if key == 274 and content.rect.bottom > self.rect.bottom:
            content.move(0, -10)
        if key == 273 and content.rect.top < self.rect.top:
            content.move(0, +10)
Ich hatte nachgelesen, dass Scrollen als Klick von Mousebutton 4 bzw. 5 weitergegeben wird, das könnte genauso funktionieren. Da mir das Verhalten vom iPhone, auf zu weites scrollen durch zurückfedern zu reagieren, recht gut gefällt, bräuchte ich spätestens jetzt einen Mechanismus derartige Animationen zu programmieren. Einen anklickbaren Rollbalken halte ich jedenfalls (wie ja auch Ubuntu 11.04) für unnötig, der hat denn noch eine Maus ohne Scroll-Rad bzw. Scroll-Funktion per Touch.

Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

[So, "einen" hab' ich noch...]

Wenn ich z.B. die Farbe eines Views über den Zeitraum von zwei Sekunden von rot in grün ändern will, brauche ich dafür eine `Animation`, genauer eine `PropertyAnimation`, genauer eine `ColorPropertyAnimation`. Alle Animationen verwalte ich in einem `Animator`:

Code: Alles auswählen

    class Animator:
        def __init__(self):
            self.animations = []
        
        def schedule(self, animation):
            self.animations.append(animation)
            animation.base = pygame.time.get_ticks()
        
        def unschedule(self, animation):
            if animation in self.animations:
                self.animations.remove(animation)
        
        def tick(self):
            time = pygame.time.get_ticks()
            for animation in self.animations:
                animation.tick(time - animation.base)
    
    Animator = Animator()
    
    class Animation:
        def __init__(self, duration=1000):
            self.duration = duration
    
        def tick(self, time):
            pass
    
    class PropertyAnimation(Animation):
        def __init__(self, target, name, duration=1000):
            Animation.__init__(self, duration)
            self.target, self.name = target, name
    
    class ColorPropertyAnimation(PropertyAnimation):
        def __init__(self, target, name, to_color, duration=1000):
            PropertyAnimation.__init__(self, target, name, duration)
            self.from_color = getattr(target, name)
            self.to_color = to_color
        
        def tick(self, time):
            if time >= self.duration:
                setattr(self.target, self.name, self.to_color)
                Animator.unschedule(self)
            
            p = float(time) / self.duration
            setattr(self.target, self.name, (
                self.interpolate(self.from_color[0], self.to_color[0], p),
                self.interpolate(self.from_color[1], self.to_color[1], p),
                self.interpolate(self.from_color[2], self.to_color[2], p)))
        
        def interpolate(self, from, to, p): return to * p + from * (1 - p)
Die Formel, wie ich Farben ineinander übergehen lasse, ist nicht perfekt, da sie nicht einfach RGB benutzen darf, aber das sei egal. Auch könnte man sich überlegen, dass nicht nur eine lineare Funktion für den Animationsverlauf benutzt werden soll. Mir geht es eher um den Mechanismus, dass ich nun in `mouse_down` eines Buttons z.B. dies machen kann (und meine alte `draw`-Methode einfach entfernen kann):

Code: Alles auswählen

    class Button(View):
        def mouse_down(self, pos):
            Animator.unschedule(self.a)
            self.a = ColorPropertyAnimation(self, "color", (250, 250, 0), 250)
            Animator.schedule(self.a)
        
        def mouse_up(self, pos):
            Animator.unschedule(self.a)
            self.a = ColorPropertyAnimation(self, "color", (120, 120, 240), 250)
            Animator.schedule(self.a)
In meiner Hauptschleife muss ich vor dem `do_draw` jetzt noch die Animationen laufen lassen:

Code: Alles auswählen

    ui.Animator.tick()
    
    view.do_draw(screen)
Will ich jetzt z.B. meinem `ScrollView` scrollen, brauche ich:

Code: Alles auswählen

    class MoveAnimation(Animation):
        def __init__(self, target, pos, duration=1000):
            Animation.__init__(self, duration)
            self.target = target
            self.to_pos = pos
            self.from_pos = target.rect.topleft
        
        def tick(self, time):
            p = float(time) / self.duration
            if p >= 1:
                self.target.set_pos(self.pos)
                self.unschedule()
            target.set_pos((
                self.interpolate(self.from_pos[0], self.to_pos[0], p),
                self.interpolate(self.from_pos[1], self.to_pos[1], p)))
War eigentlich gar nicht schwer... :)

Stefan
Antworten