Animations intervactives avec Tkinter

Préambule Par rapport au cours de NSI

L'utilisation d'objet permettant un rendu graphique n'est pas un attendu du programme de NSI. Je présente ici l'utilisation du module tkinter qui permet de contruire des interfaces graphiques permettant de démarrer des projets en python.

Prérequis

On suppose comprises les notions de module et de classe (en Python) ainsi que la notion d'événement (vus en JavaScript)

Installation tkinter

En principe, sous Windows le module est installé par défaut avec Python.

Sous un environnement de type Linux : sudo apt-get install python3-tk.

Dans un script Python :

from tkinter import Tk

# On crée la fenètre en instanciant un objet de la classe Tk :
fenetre = Tk()

# ...

# Quand tout est bien défini : on lance la boucle événementielle.
fenetre.mainloop()

Widget

Un Widget est un composant graphique de la fenêtre. Par exemple un menu, un bouton, un champ de saisie, une étiquette, un cadre composée d'autres widgets, une zone pour dessinner...

Il existe auttant de classe dans tkinter que de Widget, chacun obtenu par instanciation.

(Une bonne documentation ici.)

Label est le widget étiquette ou post-it qui peut contenir du texte et/ou une image.

Comme tous les widgets, l'objet instancié est positionnable par la méthode pack.

from tkinter import Tk, Label

fenetre = Tk()

# Création de l'étiquette pour cette fenêtre :
etiquette = Label(fenetre,text="Du texte.")
# Positionnement de l'étiquette sur la fenêtre :
etiquette.pack()

fenetre.mainloop()

Canvas

Pour pouvoir dessiner des formes géométriques ou des images et les faire bouger, ou encore positionner d'autres widgets ou cadres au pixel plès, on utilise un widget particulier : Canvas

Une fois placé dans une fenetre il apparait sous la forme d'un rectangle et définit un système de coordonnées d'origine le coin supérieur gauche et d'unité le pixel.

On dessine une balle rouge sur fond noir.

from tkinter import Tk, Canvas

fenetre = Tk()
fenetre.title("Baballe")

# Création de la zone de dessin :
dessin = Canvas(fenetre,width = 800, height=600, bd=0, bg="#000000")
dessin.pack()
# Création de la balle :
balle = dessin.create_oval(390,290,410,310,fill="#FF0000")

fenetre.mainloop()

Animation

Pour générer une animation dans une zone de dessin, il suffit de mettre régulièrement à jour les coordonnées des formes dessinées.

On n'écrit pas de boucle pour cela car une boucle générale existe déjà : celle qui est à l'écoute des événements, lancée à la dernière ligne de nos scripts (et qui ne nous a servi à rien jusqu'ici).

On va donc créer à intervalle régulier un événement qui déclenchera la mise à jour des coordonnées. On le fait avec la méthode after de la classe Tk, en précisant le delai (en ms) à attendre avant son déclenchement et le gestionnaire associé.

L'asctuce, pour générer cet événement en bloucle, est de le faire à la fin de chaque mise à jour de manière récursive.

from tkinter import Tk, Canvas

fenetre = Tk()
fenetre.title("Baballe")

dessin = Canvas(fenetre,width = 800, height=600, bd=0, bg="#000000")
dessin.pack()
balle = dessin.create_oval(390,290,410,310,fill="#FF0000")
# Définition de l'intervalle de temps pour les mises à jour en ms
dt = 30
# Vitesse de la balle en pixels par intervalle de temps
dx, dy = 5, 2

def deplacement():
    # Déplacement de la balle :
    dessin.move(balle,dx,dy)
    # Récurtion
    fenetre.after(dt,deplacement)

# Initialisation de la récurtion
deplacement()
fenetre.mainloop()

Interactivité

Pour généré de l'interactivité, il suffit d'associer à un événement possible, un gestionnaire (une fonction de cet événement)

Cela se fait avec la méthode bind d'argument l'événéement et la fonction.

L'événement s'écrit "<Button-1>" pour le clique gauche de la sourie, "<KeyPress-space>" pour l'appui sur la touche espace... et la fonction qui joue le rôle de gestionnaire de l'événemetn est à écrire selon les besoins.

from tkinter import Tk, Canvas

fenetre = Tk()
fenetre.title("Baballe")

dessin = Canvas(fenetre,width = 800, height=600, bd=0, bg="#000000")
dessin.pack()
balle = dessin.create_oval(390,290,410,310,fill="#FF0000")
dx, dy, dt = 5, 2, 30

def deplacement():
    dessin.move(balle,dx,dy)
    fenetre.after(dt,deplacement)

def clic(event):
    global dx, dy
    # position du pointeur de la souris
    x, y  = event.x, event.y
    # position de la balle
    c = dessin.coords(balle)
    c_x, c_y = (c[0]+c[2])/2, (c[1]+c[3])/2
    # Calcul de la nouvelle vitesse
    dx, dy = (x - c_x)/50, (y - c_y)/50

def stop(event):
    global dx, dy
    dx, dy = 0, 0

# Association d'un événement à son gestionnaire
fenetre.bind("<Button-1>", clic)
fenetre.bind("<KeyPress-space>", stop)

deplacement()
fenetre.mainloop()