Introduction à la programmation graphique avec Python et Kivy

PI2T Développement informatique
Séance 8
Programmation graphique
Sébastien Combéfis, Quentin Lurkin 17 mars 2016
Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution – Pas d’Utilisation Commerciale – Pas de Modification 4.0 International.
Objectifs
Programmation graphique et évènementielle
Utilisation de la librairie Kivy
Application graphique et widgets
Programmation de jeu
Dessin avec les canvas
Gestion des collisions
Librairie Kivy
Framework open-source pour créer des interfaces utilisateur Application desktop ou mobile, jeux
Plusieurs avantages offerts par la librairie
Multi-plateforme (Linux, Windows, OS X, Android, iOS)
Framework stable, API documentée
Moteur graphique basé sur OpenGL ES 2 (utilisation du GPU)
Kivy est disponible sur GitHub
Hello World (1)
Interface représentée par une classe de type App
Définition des composants dans la méthode build
Liaison d’un gestionnaire d’évènement avec bind
fromimport App from .button import Button, Label from .boxlayout import BoxLayout class HelloApp(App): def build(self): box = BoxLayout(orientation=’vertical’) box.add_widget(Label(text=’Hello World!’)) quitbtn = Button(text=’Quitter’) (on_press=self._quit) box.add_widget(quitbtn) return box def _quit(self, instance): App.get_running_app().stop() if __name__ == ’__main__’: HelloApp().run() |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Hello World (2)
Interface graphique découpée en deux parties verticalement
Label en haut et bouton en bas
Application minimale
Créer le programme dans un fichier
Convention utile pour builder les versions mobiles
Objet de type App initialise une série de choses avec run interaction avec le hardware de l’écran
discuter avec des dispositifs d’entrée comme écran multitouch, clavier, accéléromètre planification de plusieurs tâches
fromimport App App().run() |
1
2
3
Application graphique
Code d’une application graphique placé dans une classe
La classe doit être de type App
Lancement de l’application par la méthode run
Possibilité de décrire l’interface avec le langage KV
Langage balisé de description d’interfaces graphiques
fromimport App class MetroApp(App): pass if __name__ == ’__main__’: MetroApp().run() |
1
2
3
4
5
6
7
Langage KV
Fichier .kv qui porte le même nom que l’application
Donc dans notre exemple
Liste des composants avec leurs propriétés Label, champ texte, layout, onglets
#:kivy 1.0 # # author: Sébastien Combéfis # version: March 16, 2016 Label: text: "Hello World" |
1
2
3
4
5
6
7
Widget
Boite avec un comportement et pouvant contenir des boites
La classe Widget représente une boite vide
Exemples de widgets
Un Label permet d’afficher un texte
Un Button permet de répondre à un toucher ou click
Un TextInput réagit aux évènements clavier
Un widget complexe peut être construit à partir d’autres
TabbedPanel, FileChooser
Hiérarchie de widgets
Déclaration d’un widget racine par fichier KV
Il s’agit simplement de celui déclaré le plus à l’extérieur
Chaque déclaration possède un bloc specifier
Définition de propriétés du widget
Définition de widgets fils
Le widget racine est directement attaché à la fenêtre
Application Métro (1)
Fenêtre avec deux zones : contrôle en haut et affichage en bas Les tabulations définissent la hiérarchie des widgets
BoxLayout: orientation: ’vertical’ BoxLayout: orientation: ’horizontal’ size_hint: (1,.2) Label: text: ’Line’ TextInput: multiline: False Label: text: ’Station’ TextInput: multiline: False Button: text: ’Charger’ Label: text: "Entrez le numéro de la ligne et de l’arrêt qui vous intéresse." size_hint: (1,.8) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Application Métro (2)
Taille de départ de la fenêtre déterminée par configuration
from kivy.config import Config Config.set(’graphics’, ’width’, 800) Config.set(’graphics’, ’height’, 300) |
1
2
3
Évènement
Un évènement est quelque chose qui se produit
Kivy produit pleins d’évènements divers et variés, en permanence

Boucle d’évènements lancée par la méthode run
Parcours permanent des sources d’évènements
Relais des évènements vers le code de l’application
Plusieurs types d’évènements
Clic, déplacement de souris, clavier, timer, accéléromètre
Gestionnaire d’évènements
Un gestionnaire d’évènement répond à leurs occurrences
Représenté par une fonction ou méthode
Le gestionnaire reçoit des informations sur l’évènement
Source de l’évènement
Informations diverses selon le type d’évènement
class MetroApp(App): def loadschedule(self): print(’coucou’) |
1
2
3
Binding
Attache d’un gestionnaire pour un évènement sur widget cible
Utilisation d’une propriété dans le fichier KV
Propriété spécifique par type d’évènement
Par exemple, on_press pour un clic
#:import App BoxLayout: orientation: ’vertical’ BoxLayout: # Button: text: ’Charger’ on_press: App.get_running_app().loadschedule() Label: text: "Entrez le numéro de la ligne et de l’arrêt qui vous intéresse." size_hint: (1,.8) |
1
2
3
4
5
6
7
8
9
10
11
12
Créer son propre widget
Création d’un nouveau widget en créant une nouvelle classe
Le type de la classe est typiquement un widget layout
Comportement déplacé et modification du fichier KV
class MetroForm(BoxLayout): def loadschedule(self): print(’coucou’) class MetroApp(App): pass |
1
2
3
4
5
6
MetroForm: orientation: ’vertical’ BoxLayout: # Button: text: ’Charger’ on_press: root.loadschedule() # |
1
2
3
4
5
6
7
8
Propriété (1)
Lien entre le fichier KV et le code Python avec des propriétés
Objet ObjectProperty représente les propriétés dans le widget
Permet un accès direct au widget et à ses méthodes
Accessibles comme variables d’instance avec self
class MetroForm(BoxLayout): line_input = ObjectProperty() station_input = ObjectProperty() def loadschedule(self): print() print() |
1
2
3
4
5
6
7
Propriété (2)
Ajout d’un identifiant unique pour les widgets avec id
Uniquement accessible à l’intérieur du fichier KV
Lien entre les propriétés et les identifiants
MetroForm: orientation: ’vertical’ line_input: line station_input: station BoxLayout: # TextInput: id: line multiline: False # TextInput: id: station multiline: False # # |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Application Métro (3)
Récupération des horaires des prochains métro avec API STIB
class MetroForm(BoxLayout): line_input = ObjectProperty() station_input = ObjectProperty() result_output = ObjectProperty() def loadschedule(self): url = ’?line={}&halt={}’. format(, ) with urllib.request.urlopen(url) as response: = ().decode() |
1
2
3
4
5
6
7
8
9
Application Métro (4)
Réponse fournie par l’API au format XML
<waitingtimes> <stopname>ALMA</stopname> <position> <latitude>50.85</latitude> <longitude>4.453</longitude> </position> <message mode="1"> ![]() DU 17/3 A 12H AU 18/3 A 14H, SOMMET EUROPEEN. STATION SCHUMAN FERMEE. CORRESPONDANCE BUS A MAELBEEK. </message> <waitingtime> <line>1</line> <mode>M</mode> <minutes>1</minutes> <destination>GARE DE L’OUEST</destination> <message/> </waitingtime> <waitingtime> <line>1</line> <mode>M</mode> <minutes>6</minutes> <destination>GARE DE L’OUEST</destination> <message/> </waitingtime> </waitingtimes> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Application Métro (5)
Extraction des données du XML par expression régulière
Récupération de la direction du métro et du temps d’attente
with urllib.request.urlopen(url) as response: xml = ().decode() pattern = r"<waitingtime>.*?<minutes>([0-9]+)</minutes>.*?< destination>([A-Z ’]+)</destination>.*?</waitingtime>" p = re.compile(pattern) schedule = ’’ for m in p.finditer(xml): schedule += ’Vers {} : prochain dans {} minutes\n’.format(m .group(2), m.group(1)) = schedule |
1
2
3
4
5
6
7
8
Bonne pratique
Séparation recommandée entre présentation et logique
Avoir un fichier KV et un fichier Python
Layout de la fenêtre et insertion des composants en KV
En définissant des id et des propriétés
Aspects logiques et gestionnaire d’évènements dans le Python
Lien avec les composants grâce aux propriétés
Dessin
Capacité de dessin sophistiquée (statique ou animé)
Exploitation des capacités de OpenGL et SDL
OpenGL est une API de calcul d’images 2D ou 3D
Géométrie d’objets et calcul de projection à l’écran
SDL est une bibliothèque utilisée pour créer des jeux 2D
Affichage vidéo, audio numérique, périphériques (clavier, souris)
Canvas (1)
Utilisation du canvas d’un widget comme zone de dessin
Zone de dessin vierge obtenue avec des widgets de type layout
Séquence d’opérations graphiques à réaliser dans le canvas Dessin de ligne, ellipse, rectangle; choix de la couleur
TurningSquareForm: canvas: Color: rgb: [0.7, 0.2, 0] Rectangle: pos: (50, 50) size: (100, 50) |
1
2
3
4
5
6
7
Canvas (2)
Création de l’application graphique par défaut
Deux classes vides (application et widget personnalisé)
class TurningSquareForm(BoxLayout): pass class TurningSquareApp(App): pass if __name__ == ’__main__’: TurningSquareApp().run() |
1
2
3
4
5
6
7
8
Transformation (1)
Trois transformations possibles sur les objets dessinés
Rotation, translation et mise à l’échelle
TurningSquareForm: canvas: Color: rgb: [0.7, 0.2, 0] Rectangle: pos: (50, 50) size: (100, 50) Color: rgb: [0, 0.2, 0.7] Rotate: origin: (150, 50) angle: -135 axis: (0, 0, 1) Scale: origin: (150, 50) x: 0.5 y: 0.5 Rectangle: pos: (50, 50) size: (100, 50) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Transformation (2)
L’ordre d’application des transformations est important Tout comme certains paramètres comme l’origine ou l’axe
Composant déplaçable
Définition d’un nouveau composant qui est déplaçable
Le composant DraggableWidget est générique
Le composant DraggableRectangle est un cas particulier

MovableRectanglesForm: DraggableRectangle: DraggableRectangle: size: (50, 50) color: [0, 0.2, 0.7] <DraggableWidget>: size_hint: (None, None) <DraggableRectangle>: size: (100, 100) color: [0.7, 0.2, 0] canvas: Color: rgb: self.color Rectangle: pos: (10, 10) size: ([0] - 20, [1] - 20) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Application MovableRectangles
Définition d’un widget générique déplaçable
Gestionnaires pour les évènements touch (down, move et up)
class DraggableWidget(RelativeLayout): def __init__(self, **kwargs): self.__selected = None super(DraggableWidget, self).__init__(**kwargs) # class DraggableRectangle(DraggableWidget): pass class MovableRectanglesForm(BoxLayout): pass class MovableRectanglesApp(App): pass if __name__ == ’__main__’: MovableRectanglesApp().run() |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Touch down
Test de collision entre coordonnée de touch et rectangle collide_pointstest si un point est dans soi-même
Deux situations possibles
Si collision, marquer le rectangle comme sélectionné
Sinon, comportement par défaut
def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): self.select() return True return super(DraggableWidget, self).on_touch_down(touch) |
1
2
3
4
5
Touch up
Déselection du rectangle actuellement sélectionné si existant Et suppression du dessin de sélection de l’écran
def on_touch_up(self, touch): if self.__selected: self.unselect() return True return super(DraggableWidget, self).on_touch_up(touch) |
1
2
3
4
5
Touch move
Translation du rectangle vers la nouvelle position
Si cette dernière se trouve dans les limites du parent
def on_touch_move(self, touch): (x, y) = self.parent.to_parent(touch.x, touch.y) if self.__selected and self.parent.collide_point(x - self.width / 2, y - self.height / 2): self.translate(touch.x - self.__ix, touch.y - self.__iy) return True return super(DraggableWidget, self).on_touch_move(touch) |
1
2
3
4
5
6
Sélection/Désélection
Sélection d’un objet lors d’un touch down
Dessin d’un rectangle pointillé autour de l’objet
Désélection d’un objet lors d’un touch up
Suppression du rectangle pointillé
def select(self): if not self.__selected: self.__ix = self.center_x self.__iy = self.center_y with self.canvas: self.__selected = Line(rectangle=(0, 0, self.width, self.height), dash_offset=2) def unselect(self): if self.__selected: self.canvas.remove(self.__selected) self.__selected = None |
1
2
3
4
5
6
7
8
9
10
11
Translation
Translation de l’objet sélectionné
Calcul de la nouvelle position avec le shift de touch move
def translate(self, x, y): self.center_x = self.__ix = self.__ix + x self.center_y = self.__iy = self.__iy + y |
1
2
3
Livres de référence
ISBN ISBN
978-1-491-94667-1 978-1-785-28692-6
Crédits
Photos des livres depuis Amazon