Commit initial
This commit is contained in:
parent
c229442dc7
commit
85da9981d1
|
@ -0,0 +1,844 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from collections import deque
|
||||
from os import system
|
||||
from pathlib import Path
|
||||
from time import sleep, time
|
||||
from tkinter import PhotoImage
|
||||
from tkinter.font import Font
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Deque,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
print("Bibliothèque PIL chargée.", file=sys.stderr)
|
||||
PIL_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
PIL_AVAILABLE = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Literal
|
||||
|
||||
Anchor = Literal["nw", "n", "ne", "w", "center", "e", "sw", "s", "se"]
|
||||
TkEvent = tk.Event[tk.BaseWidget]
|
||||
else:
|
||||
Anchor = str
|
||||
TkEvent = tk.Event
|
||||
FltkEvent = Tuple[str, Optional[TkEvent]]
|
||||
|
||||
__all__ = [
|
||||
# gestion de fenêtre
|
||||
"cree_fenetre",
|
||||
"ferme_fenetre",
|
||||
"redimensionne_fenetre",
|
||||
"mise_a_jour",
|
||||
# dessin
|
||||
"ligne",
|
||||
"fleche",
|
||||
"polygone",
|
||||
"rectangle",
|
||||
"cercle",
|
||||
"point",
|
||||
"image",
|
||||
"texte",
|
||||
"taille_texte",
|
||||
# effacer
|
||||
"efface_tout",
|
||||
"efface",
|
||||
# utilitaires
|
||||
"attente",
|
||||
"capture_ecran",
|
||||
"touche_pressee",
|
||||
"abscisse_souris",
|
||||
"ordonnee_souris",
|
||||
"hauteur_fenetre",
|
||||
"largeur_fenetre",
|
||||
# événements
|
||||
"donne_ev",
|
||||
"attend_ev",
|
||||
"attend_clic_gauche",
|
||||
"attend_fermeture",
|
||||
"type_ev",
|
||||
"abscisse",
|
||||
"ordonnee",
|
||||
"touche",
|
||||
]
|
||||
|
||||
|
||||
class CustomCanvas:
|
||||
"""
|
||||
Classe qui encapsule tous les objets tkinter nécessaires à la création
|
||||
d'un canevas.
|
||||
"""
|
||||
|
||||
_on_osx = sys.platform.startswith("darwin")
|
||||
|
||||
_ev_mapping = {
|
||||
"ClicGauche": "<Button-1>",
|
||||
"ClicMilieu": "<Button-2>",
|
||||
"ClicDroit": "<Button-2>" if _on_osx else "<Button-3>",
|
||||
"Deplacement": "<Motion>",
|
||||
"Touche": "<Key>",
|
||||
"Redimension": "<Configure>",
|
||||
}
|
||||
|
||||
_default_ev = ["ClicGauche", "ClicDroit", "Touche"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width: int,
|
||||
height: int,
|
||||
refresh_rate: int = 100,
|
||||
events: Optional[List[str]] = None,
|
||||
resizing: bool = False,
|
||||
) -> None:
|
||||
# width and height of the canvas
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.interval = 1 / refresh_rate
|
||||
|
||||
# root Tk object
|
||||
self.root = tk.Tk()
|
||||
|
||||
# canvas attached to the root object
|
||||
self.canvas = tk.Canvas(
|
||||
self.root, width=width, height=height, highlightthickness=0
|
||||
)
|
||||
|
||||
# adding the canvas to the root window and giving it focus
|
||||
self.canvas.pack(fill=tk.BOTH, expand=tk.YES)
|
||||
self.root.resizable(width=resizing, height=resizing)
|
||||
self.canvas.focus_set()
|
||||
self.first_resize = True
|
||||
|
||||
# binding events
|
||||
self.ev_queue: Deque[FltkEvent] = deque()
|
||||
self.pressed_keys: Set[str] = set()
|
||||
self.events = CustomCanvas._default_ev if events is None else events
|
||||
self.bind_events()
|
||||
|
||||
# update for the first time
|
||||
self.last_update = time()
|
||||
self.root.update()
|
||||
|
||||
if CustomCanvas._on_osx:
|
||||
system(
|
||||
"""/usr/bin/osascript -e 'tell app "Finder" \
|
||||
to set frontmost of process "Python" to true' """
|
||||
)
|
||||
|
||||
def update(self) -> None:
|
||||
t = time()
|
||||
self.root.update()
|
||||
sleep(max(0.0, self.interval - (t - self.last_update)))
|
||||
self.last_update = time()
|
||||
|
||||
def resize(self, width: int, height: int) -> None:
|
||||
self.root.geometry(f"{int(width)}x{int(height)}")
|
||||
|
||||
def bind_events(self) -> None:
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.event_quit)
|
||||
self.canvas.bind("<Configure>", self.event_resize)
|
||||
self.canvas.bind("<KeyPress>", self.register_key)
|
||||
self.canvas.bind("<KeyRelease>", self.release_key)
|
||||
for name in self.events:
|
||||
self.bind_event(name)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def register_key(self, ev: TkEvent) -> None:
|
||||
self.pressed_keys.add(ev.keysym)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def release_key(self, ev: TkEvent) -> None:
|
||||
if ev.keysym in self.pressed_keys:
|
||||
self.pressed_keys.remove(ev.keysym)
|
||||
|
||||
def event_quit(self) -> None:
|
||||
self.ev_queue.append(("Quitte", None))
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def event_resize(self, event: TkEvent) -> None:
|
||||
if event.widget.widgetName == "canvas":
|
||||
if self.width != event.width or self.height != event.height:
|
||||
self.width, self.height = event.width, event.height
|
||||
if not self.ev_queue or self.ev_queue[-1][0] != "Redimension":
|
||||
self.ev_queue.append(("Redimension", event))
|
||||
|
||||
def bind_event(self, name: str) -> None:
|
||||
e_type = CustomCanvas._ev_mapping.get(name, name)
|
||||
|
||||
def handler(event: TkEvent, _name: str = name) -> None:
|
||||
self.ev_queue.append((_name, event))
|
||||
|
||||
self.canvas.bind(e_type, handler, "+")
|
||||
|
||||
def unbind_event(self, name: str) -> None:
|
||||
e_type = CustomCanvas._ev_mapping.get(name, name)
|
||||
self.canvas.unbind(e_type)
|
||||
|
||||
|
||||
__canevas: Optional[CustomCanvas] = None
|
||||
__img: Dict[Tuple[Path, int, int], PhotoImage] = {}
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Exceptions
|
||||
#############################################################################
|
||||
|
||||
|
||||
class TypeEvenementNonValide(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FenetreNonCree(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FenetreDejaCree(Exception):
|
||||
pass
|
||||
|
||||
|
||||
Ret = TypeVar("Ret")
|
||||
|
||||
|
||||
def _fenetre_cree(func: Callable[..., Ret]) -> Callable[..., Ret]:
|
||||
def new_func(*args: Any, **kwargs: Any) -> Ret:
|
||||
if __canevas is None:
|
||||
raise FenetreNonCree(
|
||||
'La fenêtre n\'a pas été crée avec la fonction "cree_fenetre".'
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return new_func
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Initialisation, mise à jour et fermeture
|
||||
#############################################################################
|
||||
|
||||
|
||||
def cree_fenetre(
|
||||
largeur: int, hauteur: int, frequence: int = 100,
|
||||
redimension: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Crée une fenêtre de dimensions ``largeur`` x ``hauteur`` pixels.
|
||||
:rtype:
|
||||
"""
|
||||
global __canevas
|
||||
if __canevas is not None:
|
||||
raise FenetreDejaCree(
|
||||
'La fenêtre a déjà été crée avec la fonction "cree_fenetre".'
|
||||
)
|
||||
__canevas = CustomCanvas(largeur, hauteur, frequence, resizing=redimension)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def ferme_fenetre() -> None:
|
||||
"""
|
||||
Détruit la fenêtre.
|
||||
"""
|
||||
global __canevas
|
||||
assert __canevas is not None
|
||||
__canevas.root.destroy()
|
||||
__canevas = None
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def redimensionne_fenetre(largeur: int, hauteur: int) -> None:
|
||||
"""
|
||||
Fixe les dimensions de la fenêtre à (``hauteur`` x ``largeur``) pixels.
|
||||
|
||||
Le contenu du canevas n'est pas automatiquement mis à l'échelle et doit
|
||||
être redessiné si nécessaire.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
__canevas.resize(width=largeur, height=hauteur)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def mise_a_jour() -> None:
|
||||
"""
|
||||
Met à jour la fenêtre. Les dessins ne sont affichés qu'après
|
||||
l'appel à cette fonction.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
__canevas.update()
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Fonctions de dessin
|
||||
#############################################################################
|
||||
|
||||
|
||||
# Formes géométriques
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def ligne(
|
||||
ax: float,
|
||||
ay: float,
|
||||
bx: float,
|
||||
by: float,
|
||||
couleur: str = "black",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace un segment reliant le point ``(ax, ay)`` au point ``(bx, by)``.
|
||||
|
||||
:param float ax: abscisse du premier point
|
||||
:param float ay: ordonnée du premier point
|
||||
:param float bx: abscisse du second point
|
||||
:param float by: ordonnée du second point
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_line(
|
||||
ax, ay, bx, by, fill=couleur, width=epaisseur, tags=tag
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def fleche(
|
||||
ax: float,
|
||||
ay: float,
|
||||
bx: float,
|
||||
by: float,
|
||||
couleur: str = "black",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace une flèche du point ``(ax, ay)`` au point ``(bx, by)``.
|
||||
|
||||
:param float ax: abscisse du premier point
|
||||
:param float ay: ordonnée du premier point
|
||||
:param float bx: abscisse du second point
|
||||
:param float by: ordonnée du second point
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
x, y = (bx - ax, by - ay)
|
||||
n = (x ** 2 + y ** 2) ** 0.5
|
||||
x, y = x / n, y / n
|
||||
points = [
|
||||
bx,
|
||||
by,
|
||||
bx - x * 5 - 2 * y,
|
||||
by - 5 * y + 2 * x,
|
||||
bx - x * 5 + 2 * y,
|
||||
by - 5 * y - 2 * x,
|
||||
]
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_polygon(
|
||||
points, fill=couleur, outline=couleur, width=epaisseur, tags=tag
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def polygone(
|
||||
points: List[float],
|
||||
couleur: str = "black",
|
||||
remplissage: str = "",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace un polygone dont la liste de points est fournie.
|
||||
|
||||
:param list points: liste de couples (abscisse, ordonnee) de points
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param str remplissage: couleur de fond (défaut transparent)
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_polygon(
|
||||
points, fill=remplissage, outline=couleur, width=epaisseur, tags=tag
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def rectangle(
|
||||
ax: float,
|
||||
ay: float,
|
||||
bx: float,
|
||||
by: float,
|
||||
couleur: str = "black",
|
||||
remplissage: str = "",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace un rectangle noir ayant les point ``(ax, ay)`` et ``(bx, by)``
|
||||
comme coins opposés.
|
||||
|
||||
:param float ax: abscisse du premier coin
|
||||
:param float ay: ordonnée du premier coin
|
||||
:param float bx: abscisse du second coin
|
||||
:param float by: ordonnée du second coin
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param str remplissage: couleur de fond (défaut transparent)
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_rectangle(
|
||||
ax, ay, bx, by,
|
||||
outline=couleur, fill=remplissage, width=epaisseur, tags=tag
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def cercle(
|
||||
x: float,
|
||||
y: float,
|
||||
r: float,
|
||||
couleur: str = "black",
|
||||
remplissage: str = "",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace un cercle de centre ``(x, y)`` et de rayon ``r`` en noir.
|
||||
|
||||
:param float x: abscisse du centre
|
||||
:param float y: ordonnée du centre
|
||||
:param float r: rayon
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param str remplissage: couleur de fond (défaut transparent)
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_oval(
|
||||
x - r,
|
||||
y - r,
|
||||
x + r,
|
||||
y + r,
|
||||
outline=couleur,
|
||||
fill=remplissage,
|
||||
width=epaisseur,
|
||||
tags=tag,
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def arc(
|
||||
x: float,
|
||||
y: float,
|
||||
r: float,
|
||||
ouverture: float = 90,
|
||||
depart: float = 0,
|
||||
couleur: str = "black",
|
||||
remplissage: str = "",
|
||||
epaisseur: float = 1,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Trace un arc de cercle de centre ``(x, y)``, de rayon ``r`` et
|
||||
d'angle d'ouverture ``ouverture`` (défaut : 90 degrés, dans le sens
|
||||
contraire des aiguilles d'une montre) depuis l'angle initial ``depart``
|
||||
(défaut : direction 'est').
|
||||
|
||||
:param float x: abscisse du centre
|
||||
:param float y: ordonnée du centre
|
||||
:param float r: rayon
|
||||
:param float ouverture: abscisse du centre
|
||||
:param float depart: ordonnée du centre
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param str remplissage: couleur de fond (défaut transparent)
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_arc(
|
||||
x - r,
|
||||
y - r,
|
||||
x + r,
|
||||
y + r,
|
||||
extent=ouverture,
|
||||
start=depart,
|
||||
style=tk.ARC,
|
||||
outline=couleur,
|
||||
fill=remplissage,
|
||||
width=epaisseur,
|
||||
tags=tag,
|
||||
)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def point(
|
||||
x: float, y: float,
|
||||
couleur: str = "black", epaisseur: float = 1,
|
||||
tag: str = ""
|
||||
) -> int:
|
||||
"""
|
||||
Trace un point aux coordonnées ``(x, y)`` en noir.
|
||||
|
||||
:param float x: abscisse
|
||||
:param float y: ordonnée
|
||||
:param str couleur: couleur du point (défaut 'black')
|
||||
:param float epaisseur: épaisseur de trait en pixels (défaut 1)
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return cercle(x, y, epaisseur,
|
||||
couleur=couleur, remplissage=couleur, tag=tag)
|
||||
|
||||
|
||||
# Image
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def image(
|
||||
x: float,
|
||||
y: float,
|
||||
fichier: str,
|
||||
largeur: Optional[int] = None,
|
||||
hauteur: Optional[int] = None,
|
||||
ancrage: Anchor = "center",
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Affiche l'image contenue dans ``fichier`` avec ``(x, y)`` comme centre. Les
|
||||
valeurs possibles du point d'ancrage sont ``'center'``, ``'nw'``,
|
||||
etc. Les arguments optionnels ``largeur`` et ``hauteur`` permettent de
|
||||
spécifier des dimensions maximales pour l'image (sans changement de
|
||||
proportions).
|
||||
|
||||
:param largeur: largeur de l'image
|
||||
:param hauteur: hauteur de l'image
|
||||
:param float x: abscisse du point d'ancrage
|
||||
:param float y: ordonnée du point d'ancrage
|
||||
:param str fichier: nom du fichier contenant l'image
|
||||
:param ancrage: position du point d'ancrage par rapport à l'image
|
||||
:param str tag: étiquette d'objet (défaut : pas d'étiquette)
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
if PIL_AVAILABLE:
|
||||
tk_image = _load_pil_image(fichier, hauteur, largeur)
|
||||
else:
|
||||
tk_image = _load_tk_image(fichier, hauteur, largeur)
|
||||
img_object = __canevas.canvas.create_image(
|
||||
x, y, anchor=ancrage, image=tk_image, tags=tag
|
||||
)
|
||||
return img_object
|
||||
|
||||
|
||||
def _load_tk_image(fichier: str,
|
||||
hauteur: Optional[int] = None,
|
||||
largeur: Optional[int] = None) -> PhotoImage:
|
||||
chemin = Path(fichier)
|
||||
ph_image = PhotoImage(file=fichier)
|
||||
largeur_o = ph_image.width()
|
||||
hauteur_o = ph_image.height()
|
||||
if largeur is None:
|
||||
largeur = largeur_o
|
||||
if hauteur is None:
|
||||
hauteur = hauteur_o
|
||||
zoom_l = max(1, largeur // largeur_o)
|
||||
zoom_h = max(1, hauteur // hauteur_o)
|
||||
red_l = max(1, largeur_o // largeur)
|
||||
red_h = max(1, hauteur_o // hauteur)
|
||||
largeur = largeur_o * zoom_l // red_l
|
||||
hauteur = hauteur_o * zoom_h // red_h
|
||||
if (chemin, largeur, hauteur) in __img:
|
||||
return __img[(chemin, largeur, hauteur)]
|
||||
ph_image = ph_image.zoom(zoom_l, zoom_h)
|
||||
ph_image = ph_image.subsample(red_l, red_h)
|
||||
__img[(chemin, largeur, hauteur)] = ph_image
|
||||
return ph_image
|
||||
|
||||
|
||||
def _load_pil_image(fichier: str,
|
||||
hauteur: Optional[int] = None,
|
||||
largeur: Optional[int] = None) -> PhotoImage:
|
||||
chemin = Path(fichier)
|
||||
img = Image.open(fichier)
|
||||
if largeur is None:
|
||||
largeur = img.width
|
||||
if hauteur is None:
|
||||
hauteur = img.height
|
||||
if (chemin, largeur, hauteur) in __img:
|
||||
return __img[(chemin, largeur, hauteur)]
|
||||
img = img.resize((largeur, hauteur))
|
||||
ph_image = ImageTk.PhotoImage(img)
|
||||
__img[(chemin, largeur, hauteur)] = ph_image # type:ignore
|
||||
return ph_image # type:ignore
|
||||
|
||||
|
||||
# Texte
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def texte(
|
||||
x: float,
|
||||
y: float,
|
||||
chaine: str,
|
||||
couleur: str = "black",
|
||||
ancrage: Anchor = "nw",
|
||||
police: str = "Helvetica",
|
||||
taille: int = 24,
|
||||
tag: str = "",
|
||||
) -> int:
|
||||
"""
|
||||
Affiche la chaîne ``chaine`` avec ``(x, y)`` comme point d'ancrage (par
|
||||
défaut le coin supérieur gauche).
|
||||
|
||||
:param float x: abscisse du point d'ancrage
|
||||
:param float y: ordonnée du point d'ancrage
|
||||
:param str chaine: texte à afficher
|
||||
:param str couleur: couleur de trait (défaut 'black')
|
||||
:param ancrage: position du point d'ancrage (défaut 'nw')
|
||||
:param police: police de caractères (défaut : `Helvetica`)
|
||||
:param taille: taille de police (défaut 24)
|
||||
:param tag: étiquette d'objet (défaut : pas d'étiquette
|
||||
:return: identificateur d'objet
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.create_text(
|
||||
x, y,
|
||||
text=chaine, font=(police, taille),
|
||||
tags=tag, fill=couleur, anchor=ancrage
|
||||
)
|
||||
|
||||
|
||||
def taille_texte(
|
||||
chaine: str, police: str = "Helvetica", taille: int = 24
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
Donne la largeur et la hauteur en pixel nécessaires pour afficher
|
||||
``chaine`` dans la police et la taille données.
|
||||
|
||||
:param str chaine: chaîne à mesurer
|
||||
:param police: police de caractères (défaut : `Helvetica`)
|
||||
:param taille: taille de police (défaut 24)
|
||||
:return: couple (w, h) constitué de la largeur et la hauteur de la chaîne
|
||||
en pixels (int), dans la police et la taille données.
|
||||
"""
|
||||
font = Font(family=police, size=taille)
|
||||
return font.measure(chaine), font.metrics("linespace")
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Effacer
|
||||
#############################################################################
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def efface_tout() -> None:
|
||||
"""
|
||||
Efface la fenêtre.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
__canevas.canvas.delete("all")
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def efface(objet_ou_tag: Union[int, str]) -> None:
|
||||
"""
|
||||
Efface ``objet`` de la fenêtre.
|
||||
|
||||
:param: objet ou étiquette d'objet à supprimer
|
||||
:type: ``int`` ou ``str``
|
||||
"""
|
||||
assert __canevas is not None
|
||||
__canevas.canvas.delete(objet_ou_tag)
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Utilitaires
|
||||
#############################################################################
|
||||
|
||||
|
||||
def attente(temps: float) -> None:
|
||||
start = time()
|
||||
while time() - start < temps:
|
||||
mise_a_jour()
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def capture_ecran(file: str) -> None:
|
||||
"""
|
||||
Fait une capture d'écran sauvegardée dans ``file.png``.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
__canevas.canvas.postscript( # type: ignore
|
||||
file=file + ".ps",
|
||||
height=__canevas.height,
|
||||
width=__canevas.width,
|
||||
colormode="color",
|
||||
)
|
||||
|
||||
subprocess.call(
|
||||
"convert -density 150 -geometry 100% -background white -flatten"
|
||||
" " + file + ".ps " + file + ".png",
|
||||
shell=True,
|
||||
)
|
||||
subprocess.call("rm " + file + ".ps", shell=True)
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def touche_pressee(keysym: str) -> bool:
|
||||
"""
|
||||
Renvoie `True` si ``keysym`` est actuellement pressée.
|
||||
:param keysym: symbole associé à la touche à tester.
|
||||
:return: `True` si ``keysym`` est actuellement pressée, `False` sinon.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
return keysym in __canevas.pressed_keys
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Gestions des évènements
|
||||
#############################################################################
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def donne_ev() -> Optional[FltkEvent]:
|
||||
"""
|
||||
Renvoie immédiatement l'événement en attente le plus ancien,
|
||||
ou ``None`` si aucun événement n'est en attente.
|
||||
"""
|
||||
assert __canevas is not None
|
||||
if not __canevas.ev_queue:
|
||||
return None
|
||||
return __canevas.ev_queue.popleft()
|
||||
|
||||
|
||||
def attend_ev() -> FltkEvent:
|
||||
"""Attend qu'un événement ait lieu et renvoie le premier événement qui
|
||||
se produit."""
|
||||
while True:
|
||||
ev = donne_ev()
|
||||
if ev is not None:
|
||||
return ev
|
||||
mise_a_jour()
|
||||
|
||||
|
||||
def attend_clic_gauche() -> Tuple[int, int]:
|
||||
"""Attend qu'un clic gauche sur la fenêtre ait lieu et renvoie ses
|
||||
coordonnées. **Attention**, cette fonction empêche la détection d'autres
|
||||
événements ou la fermeture de la fenêtre."""
|
||||
while True:
|
||||
ev = donne_ev()
|
||||
if ev is not None and type_ev(ev) == "ClicGauche":
|
||||
x, y = abscisse(ev), ordonnee(ev)
|
||||
assert isinstance(x, int) and isinstance(y, int)
|
||||
return x, y
|
||||
mise_a_jour()
|
||||
|
||||
|
||||
def attend_fermeture() -> None:
|
||||
"""Attend la fermeture de la fenêtre. Cette fonction renvoie None.
|
||||
**Attention**, cette fonction empêche la détection d'autres événements."""
|
||||
while True:
|
||||
ev = donne_ev()
|
||||
if ev is not None and type_ev(ev) == "Quitte":
|
||||
ferme_fenetre()
|
||||
return
|
||||
mise_a_jour()
|
||||
|
||||
|
||||
def type_ev(ev: Optional[FltkEvent]) -> Optional[str]:
|
||||
"""
|
||||
Renvoie une chaîne donnant le type de ``ev``. Les types
|
||||
possibles sont 'ClicDroit', 'ClicGauche', 'Touche' et 'Quitte'.
|
||||
Renvoie ``None`` si ``evenement`` vaut ``None``.
|
||||
"""
|
||||
return ev if ev is None else ev[0]
|
||||
|
||||
|
||||
def abscisse(ev: Optional[FltkEvent]) -> Optional[int]:
|
||||
"""
|
||||
Renvoie la coordonnée x associé à ``ev`` si elle existe, None sinon.
|
||||
"""
|
||||
x = attribut(ev, "x")
|
||||
assert isinstance(x, int) or x is None
|
||||
return x
|
||||
|
||||
|
||||
def ordonnee(ev: Optional[FltkEvent]) -> Optional[int]:
|
||||
"""
|
||||
Renvoie la coordonnée y associé à ``ev`` si elle existe, None sinon.
|
||||
"""
|
||||
y = attribut(ev, "y")
|
||||
assert isinstance(y, int) or y is None
|
||||
return y
|
||||
|
||||
|
||||
def touche(ev: Optional[FltkEvent]) -> str:
|
||||
"""
|
||||
Renvoie une chaîne correspondant à la touche associé à ``ev``,
|
||||
si elle existe.
|
||||
"""
|
||||
keysym = attribut(ev, "keysym")
|
||||
assert isinstance(keysym, str)
|
||||
return keysym
|
||||
|
||||
|
||||
def attribut(ev: Optional[FltkEvent], nom: str) -> Any:
|
||||
if ev is None:
|
||||
raise TypeEvenementNonValide(
|
||||
f"Accès à l'attribut {nom} impossible sur un événement vide"
|
||||
)
|
||||
tev, evtk = ev
|
||||
if not hasattr(evtk, nom):
|
||||
raise TypeEvenementNonValide(
|
||||
f"Accès à l'attribut {nom} impossible "
|
||||
f"sur un événement de type {tev}"
|
||||
)
|
||||
attr = getattr(evtk, nom)
|
||||
return attr if attr != "??" else None
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def abscisse_souris() -> int:
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.winfo_pointerx() - __canevas.canvas.winfo_rootx()
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def ordonnee_souris() -> int:
|
||||
assert __canevas is not None
|
||||
return __canevas.canvas.winfo_pointery() - __canevas.canvas.winfo_rooty()
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def largeur_fenetre() -> int:
|
||||
assert __canevas is not None
|
||||
return __canevas.width
|
||||
|
||||
|
||||
@_fenetre_cree
|
||||
def hauteur_fenetre() -> int:
|
||||
assert __canevas is not None
|
||||
return __canevas.height
|
|
@ -0,0 +1,10 @@
|
|||
╗╝╩╞╦╔
|
||||
╩╦╡╩╝╠
|
||||
╦╬╡╝╔╥
|
||||
╬═╥╩╚╗
|
||||
╨╦╬╔╣╗
|
||||
╬╨╩╨╞╔
|
||||
A 2 5
|
||||
D 4 2 1
|
||||
D 2 0 2
|
||||
D 4 0 3
|
|
@ -0,0 +1,10 @@
|
|||
╣╩╝╥╩╠
|
||||
╥╞╨╞╬╬
|
||||
═╚╠╡╝╥
|
||||
╨╦╝╡╬═
|
||||
║╗╝═╝╗
|
||||
╠╡╩╚╣╦
|
||||
A 4 1
|
||||
D 2 3 1
|
||||
D 3 5 2
|
||||
D 4 0 3
|
|
@ -0,0 +1,10 @@
|
|||
╣╬╣╝╚║
|
||||
╝╬╩╨╚╗
|
||||
╠╩╗╔╦╗
|
||||
╬╥╬╡╚╔
|
||||
╥╞╗╩╦╞
|
||||
╦╩╚╔╦═
|
||||
A 4 0
|
||||
D 4 5 1
|
||||
D 2 5 2
|
||||
D 0 1 3
|
|
@ -0,0 +1,10 @@
|
|||
╗╥║╠╚╝
|
||||
╨╥═╥╠╩
|
||||
╡╨╞╚═╔
|
||||
╠╣╝╝╩╗
|
||||
╩╣║╚╡╨
|
||||
╔╩╚╝╔╨
|
||||
A 5 2
|
||||
D 3 4 1
|
||||
D 5 1 2
|
||||
D 1 3 3
|
|
@ -0,0 +1,10 @@
|
|||
╔╦╦╦╦╗
|
||||
╠╩╩╩╩╣
|
||||
╠═╗╔═╣
|
||||
╠═╣╠═╣
|
||||
╠╗╠╣╔╣
|
||||
╚╩╩╩╩╝
|
||||
A 0 2
|
||||
D 1 1 1
|
||||
D 2 0 2
|
||||
D 2 5 3
|
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in New Issue