330 lines
11 KiB
Python
330 lines
11 KiB
Python
|
|
"""
|
||
|
|
Module de gestion de texte pour Pyxel
|
||
|
|
Permet d'afficher du texte et de capturer la saisie utilisateur
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pyxel
|
||
|
|
|
||
|
|
# Mapping des touches Pyxel vers les caractères
|
||
|
|
KEY_MAP = {
|
||
|
|
pyxel.KEY_A: ('a', 'A'), pyxel.KEY_B: ('b', 'B'), pyxel.KEY_C: ('c', 'C'),
|
||
|
|
pyxel.KEY_D: ('d', 'D'), pyxel.KEY_E: ('e', 'E'), pyxel.KEY_F: ('f', 'F'),
|
||
|
|
pyxel.KEY_G: ('g', 'G'), pyxel.KEY_H: ('h', 'H'), pyxel.KEY_I: ('i', 'I'),
|
||
|
|
pyxel.KEY_J: ('j', 'J'), pyxel.KEY_K: ('k', 'K'), pyxel.KEY_L: ('l', 'L'),
|
||
|
|
pyxel.KEY_M: ('m', 'M'), pyxel.KEY_N: ('n', 'N'), pyxel.KEY_O: ('o', 'O'),
|
||
|
|
pyxel.KEY_P: ('p', 'P'), pyxel.KEY_Q: ('q', 'Q'), pyxel.KEY_R: ('r', 'R'),
|
||
|
|
pyxel.KEY_S: ('s', 'S'), pyxel.KEY_T: ('t', 'T'), pyxel.KEY_U: ('u', 'U'),
|
||
|
|
pyxel.KEY_V: ('v', 'V'), pyxel.KEY_W: ('w', 'W'), pyxel.KEY_X: ('x', 'X'),
|
||
|
|
pyxel.KEY_Y: ('y', 'Y'), pyxel.KEY_Z: ('z', 'Z'),
|
||
|
|
pyxel.KEY_0: ('0', ')'), pyxel.KEY_1: ('1', '!'), pyxel.KEY_2: ('2', '@'),
|
||
|
|
pyxel.KEY_3: ('3', '#'), pyxel.KEY_4: ('4', '$'), pyxel.KEY_5: ('5', '%'),
|
||
|
|
pyxel.KEY_6: ('6', '^'), pyxel.KEY_7: ('7', '&'), pyxel.KEY_8: ('8', '*'),
|
||
|
|
pyxel.KEY_9: ('9', '('),
|
||
|
|
pyxel.KEY_SPACE: (' ', ' '),
|
||
|
|
pyxel.KEY_MINUS: ('-', '_'),
|
||
|
|
pyxel.KEY_PERIOD: ('.', '>'),
|
||
|
|
pyxel.KEY_COMMA: (',', '<'),
|
||
|
|
pyxel.KEY_SLASH: ('/', '?'),
|
||
|
|
pyxel.KEY_SEMICOLON: (';', ':'),
|
||
|
|
pyxel.KEY_APOSTROPHE: ("'", '"'),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class TextInput:
|
||
|
|
"""
|
||
|
|
Champ de saisie de texte pour Pyxel
|
||
|
|
|
||
|
|
Exemple d'utilisation:
|
||
|
|
input_field = TextInput(x=10, y=50, max_length=20)
|
||
|
|
|
||
|
|
def update():
|
||
|
|
input_field.update()
|
||
|
|
if input_field.submitted:
|
||
|
|
print(f"Texte saisi: {input_field.text}")
|
||
|
|
input_field.reset()
|
||
|
|
|
||
|
|
def draw():
|
||
|
|
input_field.draw()
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, x=0, y=0, max_length=32, width=None, color=7, bg_color=0, cursor_color=8):
|
||
|
|
"""
|
||
|
|
Initialise un champ de saisie
|
||
|
|
|
||
|
|
Args:
|
||
|
|
x, y: Position du champ
|
||
|
|
max_length: Nombre maximum de caractères
|
||
|
|
width: Largeur du champ en pixels (auto si None)
|
||
|
|
color: Couleur du texte (0-15)
|
||
|
|
bg_color: Couleur de fond (0-15, None pour transparent)
|
||
|
|
cursor_color: Couleur du curseur (0-15)
|
||
|
|
"""
|
||
|
|
self.x = x
|
||
|
|
self.y = y
|
||
|
|
self.max_length = max_length
|
||
|
|
self.width = width if width else (max_length * 4 + 4)
|
||
|
|
self.color = color
|
||
|
|
self.bg_color = bg_color
|
||
|
|
self.cursor_color = cursor_color
|
||
|
|
|
||
|
|
self.text = ""
|
||
|
|
self.cursor_pos = 0
|
||
|
|
self.active = True
|
||
|
|
self.submitted = False
|
||
|
|
self._cursor_visible = True
|
||
|
|
self._cursor_timer = 0
|
||
|
|
|
||
|
|
def update(self):
|
||
|
|
"""Met à jour le champ de saisie (à appeler dans update())"""
|
||
|
|
if not self.active:
|
||
|
|
return
|
||
|
|
|
||
|
|
self.submitted = False
|
||
|
|
|
||
|
|
# Clignotement du curseur
|
||
|
|
self._cursor_timer += 1
|
||
|
|
if self._cursor_timer >= 15:
|
||
|
|
self._cursor_timer = 0
|
||
|
|
self._cursor_visible = not self._cursor_visible
|
||
|
|
|
||
|
|
# Validation avec Entrée
|
||
|
|
if pyxel.btnp(pyxel.KEY_RETURN):
|
||
|
|
self.submitted = True
|
||
|
|
return
|
||
|
|
|
||
|
|
# Suppression avec Backspace
|
||
|
|
if pyxel.btnp(pyxel.KEY_BACKSPACE, hold=10, repeat=3):
|
||
|
|
if self.cursor_pos > 0:
|
||
|
|
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||
|
|
self.cursor_pos -= 1
|
||
|
|
|
||
|
|
# Suppression avec Delete
|
||
|
|
if pyxel.btnp(pyxel.KEY_DELETE, hold=10, repeat=3):
|
||
|
|
if self.cursor_pos < len(self.text):
|
||
|
|
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||
|
|
|
||
|
|
# Navigation avec flèches
|
||
|
|
if pyxel.btnp(pyxel.KEY_LEFT, hold=10, repeat=3):
|
||
|
|
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||
|
|
if pyxel.btnp(pyxel.KEY_RIGHT, hold=10, repeat=3):
|
||
|
|
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||
|
|
if pyxel.btnp(pyxel.KEY_HOME):
|
||
|
|
self.cursor_pos = 0
|
||
|
|
if pyxel.btnp(pyxel.KEY_END):
|
||
|
|
self.cursor_pos = len(self.text)
|
||
|
|
|
||
|
|
# Saisie de caractères
|
||
|
|
if len(self.text) < self.max_length:
|
||
|
|
shift = pyxel.btn(pyxel.KEY_LSHIFT) or pyxel.btn(pyxel.KEY_RSHIFT)
|
||
|
|
for key, chars in KEY_MAP.items():
|
||
|
|
if pyxel.btnp(key, hold=15, repeat=3):
|
||
|
|
char = chars[1] if shift else chars[0]
|
||
|
|
self.text = self.text[:self.cursor_pos] + char + self.text[self.cursor_pos:]
|
||
|
|
self.cursor_pos += 1
|
||
|
|
break
|
||
|
|
|
||
|
|
def draw(self):
|
||
|
|
"""Dessine le champ de saisie (à appeler dans draw())"""
|
||
|
|
# Fond
|
||
|
|
if self.bg_color is not None:
|
||
|
|
pyxel.rect(self.x, self.y, self.width, 9, self.bg_color)
|
||
|
|
|
||
|
|
# Bordure si actif
|
||
|
|
if self.active:
|
||
|
|
pyxel.rectb(self.x - 1, self.y - 1, self.width + 2, 11, self.color)
|
||
|
|
|
||
|
|
# Texte
|
||
|
|
pyxel.text(self.x + 2, self.y + 1, self.text, self.color)
|
||
|
|
|
||
|
|
# Curseur
|
||
|
|
if self.active and self._cursor_visible:
|
||
|
|
cursor_x = self.x + 2 + self.cursor_pos * 4
|
||
|
|
pyxel.rect(cursor_x, self.y + 1, 1, 7, self.cursor_color)
|
||
|
|
|
||
|
|
def reset(self):
|
||
|
|
"""Réinitialise le champ"""
|
||
|
|
self.text = ""
|
||
|
|
self.cursor_pos = 0
|
||
|
|
self.submitted = False
|
||
|
|
|
||
|
|
def set_text(self, text):
|
||
|
|
"""Définit le texte du champ"""
|
||
|
|
self.text = text[:self.max_length]
|
||
|
|
self.cursor_pos = len(self.text)
|
||
|
|
|
||
|
|
|
||
|
|
class TextBox:
|
||
|
|
"""
|
||
|
|
Zone de texte multiligne avec retour à la ligne automatique
|
||
|
|
|
||
|
|
Exemple d'utilisation:
|
||
|
|
textbox = TextBox(x=10, y=10, width=100, color=7)
|
||
|
|
textbox.set_text("Ceci est un long texte qui sera automatiquement découpé en plusieurs lignes.")
|
||
|
|
|
||
|
|
def draw():
|
||
|
|
textbox.draw()
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, x=0, y=0, width=100, color=7, line_spacing=8):
|
||
|
|
"""
|
||
|
|
Initialise une zone de texte
|
||
|
|
|
||
|
|
Args:
|
||
|
|
x, y: Position de la zone
|
||
|
|
width: Largeur en pixels
|
||
|
|
color: Couleur du texte (0-15)
|
||
|
|
line_spacing: Espacement entre les lignes
|
||
|
|
"""
|
||
|
|
self.x = x
|
||
|
|
self.y = y
|
||
|
|
self.width = width
|
||
|
|
self.color = color
|
||
|
|
self.line_spacing = line_spacing
|
||
|
|
self.lines = []
|
||
|
|
|
||
|
|
def set_text(self, text):
|
||
|
|
"""Définit le texte avec retour à la ligne automatique"""
|
||
|
|
self.lines = []
|
||
|
|
chars_per_line = self.width // 4
|
||
|
|
|
||
|
|
paragraphs = text.split('\n')
|
||
|
|
for paragraph in paragraphs:
|
||
|
|
words = paragraph.split(' ')
|
||
|
|
current_line = ""
|
||
|
|
|
||
|
|
for word in words:
|
||
|
|
test_line = current_line + (" " if current_line else "") + word
|
||
|
|
if len(test_line) <= chars_per_line:
|
||
|
|
current_line = test_line
|
||
|
|
else:
|
||
|
|
if current_line:
|
||
|
|
self.lines.append(current_line)
|
||
|
|
# Mot trop long, on le coupe
|
||
|
|
while len(word) > chars_per_line:
|
||
|
|
self.lines.append(word[:chars_per_line])
|
||
|
|
word = word[chars_per_line:]
|
||
|
|
current_line = word
|
||
|
|
|
||
|
|
if current_line:
|
||
|
|
self.lines.append(current_line)
|
||
|
|
|
||
|
|
def draw(self):
|
||
|
|
"""Dessine la zone de texte"""
|
||
|
|
for i, line in enumerate(self.lines):
|
||
|
|
pyxel.text(self.x, self.y + i * self.line_spacing, line, self.color)
|
||
|
|
|
||
|
|
def get_height(self):
|
||
|
|
"""Retourne la hauteur totale du texte"""
|
||
|
|
return len(self.lines) * self.line_spacing
|
||
|
|
|
||
|
|
|
||
|
|
class TypeWriter:
|
||
|
|
"""
|
||
|
|
Effet machine à écrire pour afficher du texte progressivement
|
||
|
|
|
||
|
|
Exemple d'utilisation:
|
||
|
|
typewriter = TypeWriter(x=10, y=10, speed=3)
|
||
|
|
typewriter.set_text("Bienvenue dans le jeu...")
|
||
|
|
|
||
|
|
def update():
|
||
|
|
typewriter.update()
|
||
|
|
|
||
|
|
def draw():
|
||
|
|
typewriter.draw()
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, x=0, y=0, width=100, color=7, speed=2, line_spacing=8):
|
||
|
|
"""
|
||
|
|
Args:
|
||
|
|
x, y: Position
|
||
|
|
width: Largeur en pixels
|
||
|
|
color: Couleur du texte
|
||
|
|
speed: Frames entre chaque caractère (plus bas = plus rapide)
|
||
|
|
line_spacing: Espacement entre les lignes
|
||
|
|
"""
|
||
|
|
self.textbox = TextBox(x, y, width, color, line_spacing)
|
||
|
|
self.speed = speed
|
||
|
|
self.full_text = ""
|
||
|
|
self.displayed_chars = 0
|
||
|
|
self.timer = 0
|
||
|
|
self.finished = False
|
||
|
|
|
||
|
|
def set_text(self, text):
|
||
|
|
"""Définit le texte à afficher progressivement"""
|
||
|
|
self.full_text = text
|
||
|
|
self.displayed_chars = 0
|
||
|
|
self.timer = 0
|
||
|
|
self.finished = False
|
||
|
|
|
||
|
|
def update(self):
|
||
|
|
"""Met à jour l'animation"""
|
||
|
|
if self.finished:
|
||
|
|
return
|
||
|
|
|
||
|
|
self.timer += 1
|
||
|
|
if self.timer >= self.speed:
|
||
|
|
self.timer = 0
|
||
|
|
self.displayed_chars += 1
|
||
|
|
if self.displayed_chars >= len(self.full_text):
|
||
|
|
self.displayed_chars = len(self.full_text)
|
||
|
|
self.finished = True
|
||
|
|
|
||
|
|
self.textbox.set_text(self.full_text[:self.displayed_chars])
|
||
|
|
|
||
|
|
def draw(self):
|
||
|
|
"""Dessine le texte"""
|
||
|
|
self.textbox.draw()
|
||
|
|
|
||
|
|
def skip(self):
|
||
|
|
"""Affiche tout le texte immédiatement"""
|
||
|
|
self.displayed_chars = len(self.full_text)
|
||
|
|
self.finished = True
|
||
|
|
self.textbox.set_text(self.full_text)
|
||
|
|
|
||
|
|
def is_finished(self):
|
||
|
|
"""Retourne True si tout le texte est affiché"""
|
||
|
|
return self.finished
|
||
|
|
|
||
|
|
|
||
|
|
# --- Exemple d'utilisation ---
|
||
|
|
if __name__ == "__main__":
|
||
|
|
|
||
|
|
class Demo:
|
||
|
|
def __init__(self):
|
||
|
|
pyxel.init(200, 150, title="Demo TextInput")
|
||
|
|
|
||
|
|
self.input_field = TextInput(x=10, y=60, max_length=25, color=7)
|
||
|
|
self.typewriter = TypeWriter(x=10, y=10, width=180, speed=2)
|
||
|
|
self.typewriter.set_text("Bienvenue ! Entrez votre nom ci-dessous et appuyez sur Entree.")
|
||
|
|
|
||
|
|
self.messages = []
|
||
|
|
|
||
|
|
pyxel.run(self.update, self.draw)
|
||
|
|
|
||
|
|
def update(self):
|
||
|
|
self.typewriter.update()
|
||
|
|
self.input_field.update()
|
||
|
|
|
||
|
|
# Skip le typewriter avec espace
|
||
|
|
if pyxel.btnp(pyxel.KEY_SPACE) and not self.input_field.active:
|
||
|
|
self.typewriter.skip()
|
||
|
|
|
||
|
|
# Validation de la saisie
|
||
|
|
if self.input_field.submitted and self.input_field.text:
|
||
|
|
self.messages.append(f"Bonjour, {self.input_field.text} !")
|
||
|
|
self.input_field.reset()
|
||
|
|
|
||
|
|
def draw(self):
|
||
|
|
pyxel.cls(0)
|
||
|
|
|
||
|
|
self.typewriter.draw()
|
||
|
|
|
||
|
|
pyxel.text(10, 50, "Votre nom:", 6)
|
||
|
|
self.input_field.draw()
|
||
|
|
|
||
|
|
# Afficher les messages
|
||
|
|
for i, msg in enumerate(self.messages[-3:]):
|
||
|
|
pyxel.text(10, 90 + i * 10, msg, 11)
|
||
|
|
|
||
|
|
Demo()
|