docs(crepe-carottage): essai d'ui avec une variable et sa description

This commit is contained in:
gwen 2026-06-29 22:19:13 +02:00
parent 628f3fb7ca
commit 1fe0875a5a
3 changed files with 1317 additions and 0 deletions

468
doc/crepe.py Normal file
View file

@ -0,0 +1,468 @@
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
from textual import events
from typing import List
class CustomListItem(ListItem):
"""ListItem personnalisé pour stocker le terme et sa définition"""
def __init__(self, term: str, definition: str) -> None:
super().__init__(Label(term))
self.term = term
self.definition = definition
class DictionaryApp(App):
"""Application de dictionnaire avec autocomplétion et aperçu en temps réel"""
CSS = """
Screen {
background: #1e1e2e;
}
#main-container {
height: 100%;
width: 100%;
padding: 1;
background: #1e1e2e;
}
#search-container {
height: auto;
min-height: 5;
max-height: 8;
margin-bottom: 1;
background: #2d2d44;
padding: 1;
}
#results-container {
height: 100%;
background: #2d2d44;
padding: 1;
overflow-y: auto;
}
#search-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
Input {
background: #3d3d5c;
color: #cdd6f4;
border: solid #585b70;
padding: 1;
width: 100%;
margin-top: 1;
}
Input:focus {
border: solid #89b4fa;
}
#results-list {
background: #3d3d5c;
color: #cdd6f4;
height: 100%;
min-height: 10;
border: solid #585b70;
padding: 0;
}
ListItem {
padding: 1;
background: #3d3d5c;
color: #cdd6f4;
width: 100%;
}
ListItem:hover {
background: #45476a;
}
ListItem:focus {
background: #89b4fa;
color: #1e1e2e;
}
ListItem > Label {
width: 100%;
}
#results-label {
color: #a6e3a1;
padding-bottom: 1;
width: 100%;
}
#preview-container {
background: #2d2d44;
padding: 1;
margin-top: 1;
min-height: 3;
max-height: 5;
border: solid #585b70;
color: #cdd6f4;
}
#preview-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
#preview-content {
color: #f9e2af;
padding: 1;
background: #3d3d5c;
min-height: 2;
width: 100%;
}
#definition-box {
background: #2d2d44;
padding: 1;
margin-top: 1;
min-height: 8;
max-height: 30;
border: solid #585b70;
color: #cdd6f4;
}
#definition-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
#definition-content {
color: #cdd6f4;
padding: 1;
background: #3d3d5c;
height: 100%;
min-height: 5;
overflow-y: scroll;
width: 100%;
}
.highlight-term {
color: #f9e2af;
text-style: bold;
background: #45476a;
padding: 1;
}
"""
def __init__(self):
super().__init__()
# Dictionnaire de termes avec leurs définitions
self.dictionary = {
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
"Docker": "Plateforme de conteneurisation pour applications distribuées",
"Git": "Système de contrôle de version distribué pour le suivi de code",
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
}
# Liste triée des termes pour la recherche
self.terms = sorted(self.dictionary.keys())
self.filtered_terms = self.terms.copy()
self.current_input = ""
self.last_selected_term = None
def compose(self) -> ComposeResult:
"""Compose l'interface utilisateur"""
yield Header()
yield Container(
Container(
# Zone de recherche
Container(
Label("🔍 Rechercher un terme :", id="search-label"),
Input(placeholder="Tapez un terme...", id="search-input"),
id="search-container"
),
# Zone d'aperçu du texte en cours
Container(
Label("✏️ Texte saisi :", id="preview-label"),
Static("En attente de saisie...", id="preview-content"),
id="preview-container"
),
# Résultats et définition
Container(
Label("📋 0 terme trouvé", id="results-label"),
ListView(id="results-list"),
Container(
Label("📖 Définition", id="definition-label"),
Static("Sélectionnez un terme pour voir sa définition", id="definition-content"),
id="definition-box"
),
id="results-container"
),
id="main-container"
),
)
yield Footer()
def on_mount(self) -> None:
"""Initialisation après le montage"""
search_input = self.query_one("#search-input")
search_input.focus()
self.update_results()
self.apply_responsive_styles()
def on_resize(self, event: events.Resize) -> None:
"""Gère le redimensionnement du terminal"""
self.apply_responsive_styles()
self.update_list_height()
def apply_responsive_styles(self) -> None:
"""Applique les styles responsifs en fonction de la taille du terminal"""
width = self.size.width
height = self.size.height
# Ajuster les paddings selon la largeur
main_container = self.query_one("#main-container")
search_container = self.query_one("#search-container")
results_container = self.query_one("#results-container")
preview_container = self.query_one("#preview-container")
definition_box = self.query_one("#definition-box")
definition_content = self.query_one("#definition-content")
if width < 80:
# Petit terminal
main_container.styles.padding = 1
search_container.styles.padding = 1
results_container.styles.padding = 1
preview_container.styles.padding = 1
definition_box.styles.padding = 1
definition_content.styles.padding = 1
definition_box.styles.min_height = 6
definition_box.styles.max_height = 12
preview_container.styles.min_height = 2
preview_container.styles.max_height = 3
search_container.styles.min_height = 4
search_container.styles.max_height = 6
elif width > 120 and height > 40:
# Grand terminal
main_container.styles.padding = 2
search_container.styles.padding = 2
results_container.styles.padding = 2
preview_container.styles.padding = 2
definition_box.styles.padding = 2
definition_content.styles.padding = 2
definition_box.styles.min_height = 12
definition_box.styles.max_height = 30
preview_container.styles.min_height = 4
preview_container.styles.max_height = 6
search_container.styles.min_height = 6
search_container.styles.max_height = 10
else:
# Terminal moyen (par défaut)
main_container.styles.padding = 1
search_container.styles.padding = 1
results_container.styles.padding = 1
preview_container.styles.padding = 1
definition_box.styles.padding = 1
definition_content.styles.padding = 1
definition_box.styles.min_height = 8
definition_box.styles.max_height = 20
preview_container.styles.min_height = 3
preview_container.styles.max_height = 5
search_container.styles.min_height = 5
search_container.styles.max_height = 8
# Ajuster la hauteur de la définition selon la hauteur du terminal
if height < 30:
definition_box.styles.min_height = 4
definition_box.styles.max_height = 8
preview_container.styles.min_height = 2
preview_container.styles.max_height = 3
elif height > 50:
definition_box.styles.min_height = 10
definition_box.styles.max_height = 25
preview_container.styles.min_height = 4
preview_container.styles.max_height = 6
def update_list_height(self) -> None:
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
try:
results_list = self.query_one("#results-list")
available_height = self.size.height - self.calculate_fixed_height()
# Ajuster la hauteur de la liste
if available_height > 15:
results_list.styles.height = available_height - 2
elif available_height > 10:
results_list.styles.height = available_height
else:
results_list.styles.height = 8
except Exception:
pass
def calculate_fixed_height(self) -> int:
"""Calcule la hauteur fixe des éléments (header, footer, search, preview, definition)"""
try:
search_container = self.query_one("#search-container")
preview_container = self.query_one("#preview-container")
definition_box = self.query_one("#definition-box")
# Récupérer les hauteurs actuelles
search_height = search_container.styles.min_height or 5
preview_height = preview_container.styles.min_height or 3
definition_height = definition_box.styles.min_height or 8
# Estimer la hauteur fixe totale
if isinstance(search_height, (int, float)):
search_h = search_height
else:
search_h = 5
if isinstance(preview_height, (int, float)):
preview_h = preview_height
else:
preview_h = 3
if isinstance(definition_height, (int, float)):
definition_h = definition_height
else:
definition_h = 8
# Header + Footer + margins + padding
fixed_height = 2 + search_h + preview_h + definition_h + 6
return int(fixed_height)
except Exception:
return 25 # Valeur par défaut en cas d'erreur
def on_input_changed(self, event: Input.Changed) -> None:
"""Gère les changements dans le champ de recherche"""
if event.input.id == "search-input":
self.current_input = event.value
self.update_preview()
self.update_results()
def update_preview(self) -> None:
"""Met à jour l'aperçu du texte saisi"""
preview_content = self.query_one("#preview-content")
if self.current_input:
# Afficher le texte avec mise en forme
preview_text = f"📝 {self.current_input}"
# Si le texte correspond à un terme connu, le mettre en évidence
matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()]
if matching_terms:
preview_text = f"{self.current_input} (terme trouvé !)"
preview_content.update(preview_text)
else:
preview_content.update("En attente de saisie...")
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Gère la soumission du champ de recherche (touche Entrée)"""
if event.input.id == "search-input":
results_list = self.query_one("#results-list")
if results_list.children:
first_item = results_list.children[0]
if hasattr(first_item, 'term') and hasattr(first_item, 'definition'):
self.show_definition(first_item.term, first_item.definition)
def update_results(self) -> None:
"""Met à jour la liste des résultats"""
search_text = self.current_input.lower().strip()
# Filtrer les termes
if search_text:
self.filtered_terms = [
term for term in self.terms
if search_text in term.lower()
]
else:
self.filtered_terms = self.terms.copy()
# Mettre à jour la ListView
results_list = self.query_one("#results-list")
results_list.clear()
for term in self.filtered_terms:
definition = self.dictionary[term]
item = CustomListItem(term, definition)
results_list.append(item)
# Mettre à jour le compteur
results_label = self.query_one("#results-label")
count = len(self.filtered_terms)
if count == 0:
results_label.update("❌ Aucun résultat trouvé")
else:
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Gère la sélection d'un terme dans la liste"""
if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'):
self.show_definition(event.item.term, event.item.definition)
def show_definition(self, term: str, definition: str) -> None:
"""Affiche la définition du terme sélectionné"""
definition_content = self.query_one("#definition-content")
# Mettre en valeur le terme dans la définition
definition_text = f"[b]{term}[/b]\n\n{definition}"
definition_content.update(definition_text)
# Mettre à jour le champ de recherche avec le terme sélectionné
search_input = self.query_one("#search-input")
search_input.value = term
self.current_input = term
self.update_preview()
self.update_results()
self.last_selected_term = term
def on_key(self, event: events.Key) -> None:
"""Gère les touches spéciales"""
if event.key == "escape":
# Effacer la recherche
search_input = self.query_one("#search-input")
search_input.value = ""
self.current_input = ""
self.update_preview()
self.update_results()
search_input.focus()
elif event.key == "ctrl+f":
# Focus sur la recherche
self.query_one("#search-input").focus()
elif event.key == "ctrl+d":
# Effacer la définition
definition_content = self.query_one("#definition-content")
definition_content.update("Sélectionnez un terme pour voir sa définition")
# Point d'entrée de l'application
if __name__ == "__main__":
app = DictionaryApp()
app.run()

389
doc/crepe.py.ori Normal file
View file

@ -0,0 +1,389 @@
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
from textual import events
from textual.message import Message
from typing import List, Optional
class CustomListItem(ListItem):
"""ListItem personnalisé pour stocker le terme et sa définition"""
def __init__(self, term: str, definition: str) -> None:
super().__init__(Label(term))
self.term = term
self.definition = definition
class DictionaryApp(App):
"""Application de dictionnaire avec autocomplétion et responsive design"""
CSS = """
Screen {
background: #1e1e2e;
}
#main-container {
height: 100%;
width: 100%;
padding: 1;
background: #1e1e2e;
}
#search-container {
height: auto;
min-height: 5;
max-height: 8;
margin-bottom: 1;
background: #2d2d44;
padding: 1;
}
#results-container {
height: 100%;
background: #2d2d44;
padding: 1;
overflow-y: auto;
}
#search-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
Input {
background: #3d3d5c;
color: #cdd6f4;
border: solid #585b70;
padding: 1;
width: 100%;
margin-top: 1;
}
Input:focus {
border: solid #89b4fa;
}
#results-list {
background: #3d3d5c;
color: #cdd6f4;
height: 100%;
min-height: 10;
border: solid #585b70;
padding: 0;
}
ListItem {
padding: 1;
background: #3d3d5c;
color: #cdd6f4;
width: 100%;
}
ListItem:hover {
background: #45476a;
}
ListItem:focus {
background: #89b4fa;
color: #1e1e2e;
}
ListItem > Label {
width: 100%;
}
#results-label {
color: #a6e3a1;
padding-bottom: 1;
width: 100%;
}
#definition-box {
background: #2d2d44;
padding: 1;
margin-top: 1;
min-height: 8;
max-height: 30;
border: solid #585b70;
color: #cdd6f4;
}
#definition-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
#definition-content {
color: #cdd6f4;
padding: 1;
background: #3d3d5c;
height: 100%;
min-height: 5;
overflow-y: scroll;
width: 100%;
}
"""
def __init__(self):
super().__init__()
# Dictionnaire de termes avec leurs définitions
self.dictionary = {
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
"Docker": "Plateforme de conteneurisation pour applications distribuées",
"Git": "Système de contrôle de version distribué pour le suivi de code",
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
}
# Liste triée des termes pour la recherche
self.terms = sorted(self.dictionary.keys())
self.filtered_terms = self.terms.copy()
self.current_input = ""
self.last_selected_term = None
def compose(self) -> ComposeResult:
"""Compose l'interface utilisateur"""
yield Header()
yield Container(
Container(
Container(
Label("🔍 Rechercher un terme :", id="search-label"),
Input(placeholder="Tapez un terme...", id="search-input"),
id="search-container"
),
Container(
Label("📋 0 terme trouvé", id="results-label"),
ListView(id="results-list"),
Container(
Label("📖 Définition", id="definition-label"),
Static("Sélectionnez un terme pour voir sa définition", id="definition-content"),
id="definition-box"
),
id="results-container"
),
id="main-container"
),
)
yield Footer()
def on_mount(self) -> None:
"""Initialisation après le montage"""
search_input = self.query_one("#search-input")
search_input.focus()
self.update_results()
self.apply_responsive_styles()
def on_resize(self, event: events.Resize) -> None:
"""Gère le redimensionnement du terminal"""
self.apply_responsive_styles()
self.update_list_height()
def apply_responsive_styles(self) -> None:
"""Applique les styles responsifs en fonction de la taille du terminal"""
width = self.size.width
height = self.size.height
# Ajuster les paddings selon la largeur
main_container = self.query_one("#main-container")
search_container = self.query_one("#search-container")
results_container = self.query_one("#results-container")
definition_box = self.query_one("#definition-box")
definition_content = self.query_one("#definition-content")
if width < 80:
# Petit terminal
main_container.styles.padding = 0.5
search_container.styles.padding = 0.5
results_container.styles.padding = 0.5
definition_box.styles.padding = 0.5
definition_content.styles.padding = 0.5
definition_box.styles.min_height = 6
definition_box.styles.max_height = 12
# Réduire la police ou l'espacement
search_container.styles.min_height = 4
search_container.styles.max_height = 6
elif width > 120 and height > 40:
# Grand terminal
main_container.styles.padding = 2
search_container.styles.padding = 2
results_container.styles.padding = 2
definition_box.styles.padding = 2
definition_content.styles.padding = 2
definition_box.styles.min_height = 12
definition_box.styles.max_height = 30
search_container.styles.min_height = 6
search_container.styles.max_height = 10
else:
# Terminal moyen (par défaut)
main_container.styles.padding = 1
search_container.styles.padding = 1
results_container.styles.padding = 1
definition_box.styles.padding = 1
definition_content.styles.padding = 1
definition_box.styles.min_height = 8
definition_box.styles.max_height = 20
search_container.styles.min_height = 5
search_container.styles.max_height = 8
# Ajuster la hauteur de la définition selon la hauteur du terminal
if height < 30:
definition_box.styles.min_height = 4
definition_box.styles.max_height = 8
elif height > 50:
definition_box.styles.min_height = 10
definition_box.styles.max_height = 25
def update_list_height(self) -> None:
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
try:
results_list = self.query_one("#results-list")
available_height = self.size.height - self.calculate_fixed_height()
# Ajuster la hauteur de la liste
if available_height > 15:
results_list.styles.height = available_height - 2
elif available_height > 10:
results_list.styles.height = available_height
else:
results_list.styles.height = 8
except Exception:
pass
def calculate_fixed_height(self) -> int:
"""Calcule la hauteur fixe des éléments (header, footer, search, definition)"""
try:
search_container = self.query_one("#search-container")
definition_box = self.query_one("#definition-box")
# Récupérer les hauteurs actuelles
search_height = search_container.styles.min_height or 5
definition_height = definition_box.styles.min_height or 8
# Estimer la hauteur fixe totale
if isinstance(search_height, (int, float)):
search_h = search_height
else:
search_h = 5
if isinstance(definition_height, (int, float)):
definition_h = definition_height
else:
definition_h = 8
# Header + Footer + margins + padding
fixed_height = 2 + search_h + definition_h + 4
return int(fixed_height)
except Exception:
return 20 # Valeur par défaut en cas d'erreur
def on_input_changed(self, event: Input.Changed) -> None:
"""Gère les changements dans le champ de recherche"""
if event.input.id == "search-input":
self.current_input = event.value
self.update_results()
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Gère la soumission du champ de recherche (touche Entrée)"""
if event.input.id == "search-input":
results_list = self.query_one("#results-list")
if results_list.children:
first_item = results_list.children[0]
if hasattr(first_item, 'term') and hasattr(first_item, 'definition'):
self.show_definition(first_item.term, first_item.definition)
def update_results(self) -> None:
"""Met à jour la liste des résultats"""
search_text = self.current_input.lower().strip()
# Filtrer les termes
if search_text:
self.filtered_terms = [
term for term in self.terms
if search_text in term.lower()
]
else:
self.filtered_terms = self.terms.copy()
# Mettre à jour la ListView
results_list = self.query_one("#results-list")
results_list.clear()
for term in self.filtered_terms:
definition = self.dictionary[term]
item = CustomListItem(term, definition)
results_list.append(item)
# Mettre à jour le compteur
results_label = self.query_one("#results-label")
count = len(self.filtered_terms)
if count == 0:
results_label.update("❌ Aucun résultat trouvé")
else:
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Gère la sélection d'un terme dans la liste"""
if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'):
self.show_definition(event.item.term, event.item.definition)
def show_definition(self, term: str, definition: str) -> None:
"""Affiche la définition du terme sélectionné"""
definition_content = self.query_one("#definition-content")
# Mettre en valeur le terme dans la définition
definition_text = f"[b]{term}[/b]\n\n{definition}"
definition_content.update(definition_text)
# Mettre à jour le champ de recherche avec le terme sélectionné
search_input = self.query_one("#search-input")
search_input.value = term
self.current_input = term
self.update_results()
self.last_selected_term = term
def on_key(self, event: events.Key) -> None:
"""Gère les touches spéciales"""
if event.key == "escape":
# Effacer la recherche
search_input = self.query_one("#search-input")
search_input.value = ""
self.current_input = ""
self.update_results()
search_input.focus()
elif event.key == "ctrl+f":
# Focus sur la recherche
self.query_one("#search-input").focus()
elif event.key == "ctrl+d":
# Effacer la définition
definition_content = self.query_one("#definition-content")
definition_content.update("Sélectionnez un terme pour voir sa définition")
# Point d'entrée de l'application
if __name__ == "__main__":
app = DictionaryApp()
app.run()

460
doc/crepe3.py Normal file
View file

@ -0,0 +1,460 @@
from textual.app import App, ComposeResult
from textual.containers import Container, Vertical
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
from textual import events
from typing import List, Optional
class TermListItem(ListItem):
"""ListItem personnalisé pour afficher un terme et sa définition"""
def __init__(self, term: str, definition: str, expanded: bool = False) -> None:
self.term = term
self.definition = definition
self.expanded = expanded
# Créer le contenu avec le terme et éventuellement la définition
content = self._create_content()
super().__init__(content)
def _create_content(self):
"""Crée le contenu du ListItem avec ou sans définition"""
if self.expanded:
return Vertical(
Label(f"📌 {self.term}", classes="term-title"),
Label(f" {self.definition}", classes="term-definition"),
classes="term-expanded"
)
else:
return Label(f"📌 {self.term}", classes="term-title")
def toggle_expand(self):
"""Bascule l'état d'expansion du terme"""
self.expanded = not self.expanded
# Mettre à jour le contenu
self._update_content()
def _update_content(self):
"""Met à jour le contenu du ListItem"""
new_content = self._create_content()
# Remplacer l'ancien contenu
self.remove_children()
self.mount(new_content)
class DictionaryApp(App):
"""Application de dictionnaire avec affichage de la définition sous le terme"""
CSS = """
Screen {
background: #1e1e2e;
}
#main-container {
height: 100%;
width: 100%;
padding: 1;
background: #1e1e2e;
}
#search-container {
height: auto;
min-height: 5;
max-height: 8;
margin-bottom: 1;
background: #2d2d44;
padding: 1;
}
#results-container {
height: 100%;
background: #2d2d44;
padding: 1;
overflow-y: auto;
}
#search-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
Input {
background: #3d3d5c;
color: #cdd6f4;
border: solid #585b70;
padding: 1;
width: 100%;
margin-top: 1;
}
Input:focus {
border: solid #89b4fa;
}
#results-list {
background: #3d3d5c;
color: #cdd6f4;
height: 100%;
min-height: 10;
border: solid #585b70;
padding: 0;
}
ListItem {
padding: 1;
background: #3d3d5c;
color: #cdd6f4;
width: 100%;
border-bottom: solid #45476a;
}
ListItem:hover {
background: #45476a;
}
ListItem:focus {
background: #45476a;
}
.term-title {
color: #89b4fa;
text-style: bold;
width: 100%;
padding: 1;
}
.term-definition {
color: #cdd6f4;
padding: 1;
padding-left: 2;
background: #3d3d5c;
width: 100%;
border-left: solid #89b4fa;
margin-top: 1;
}
.term-expanded {
background: #2d2d44;
padding: 0;
}
#results-label {
color: #a6e3a1;
padding-bottom: 1;
width: 100%;
}
#preview-container {
background: #2d2d44;
padding: 1;
margin-top: 1;
min-height: 3;
max-height: 5;
border: solid #585b70;
color: #cdd6f4;
}
#preview-label {
color: #89b4fa;
text-style: bold;
padding-bottom: 1;
width: 100%;
}
#preview-content {
color: #f9e2af;
padding: 1;
background: #3d3d5c;
min-height: 2;
width: 100%;
}
"""
def __init__(self):
super().__init__()
# Dictionnaire de termes avec leurs définitions
self.dictionary = {
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
"Docker": "Plateforme de conteneurisation pour applications distribuées",
"Git": "Système de contrôle de version distribué pour le suivi de code",
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
}
# Liste triée des termes pour la recherche
self.terms = sorted(self.dictionary.keys())
self.filtered_terms = self.terms.copy()
self.current_input = ""
self.last_selected_term = None
self.expanded_terms = set() # Ensemble des termes actuellement expansés
def compose(self) -> ComposeResult:
"""Compose l'interface utilisateur"""
yield Header()
yield Container(
Container(
# Zone de recherche
Container(
Label("🔍 Rechercher un terme :", id="search-label"),
Input(placeholder="Tapez un terme...", id="search-input"),
id="search-container"
),
# Zone d'aperçu du texte en cours
Container(
Label("✏️ Texte saisi :", id="preview-label"),
Static("En attente de saisie...", id="preview-content"),
id="preview-container"
),
# Résultats
Container(
Label("📋 0 terme trouvé", id="results-label"),
ListView(id="results-list"),
id="results-container"
),
id="main-container"
),
)
yield Footer()
def on_mount(self) -> None:
"""Initialisation après le montage"""
search_input = self.query_one("#search-input")
search_input.focus()
self.update_results()
self.apply_responsive_styles()
def on_resize(self, event: events.Resize) -> None:
"""Gère le redimensionnement du terminal"""
self.apply_responsive_styles()
self.update_list_height()
def apply_responsive_styles(self) -> None:
"""Applique les styles responsifs en fonction de la taille du terminal"""
width = self.size.width
height = self.size.height
# Ajuster les paddings selon la largeur
main_container = self.query_one("#main-container")
search_container = self.query_one("#search-container")
results_container = self.query_one("#results-container")
preview_container = self.query_one("#preview-container")
if width < 80:
# Petit terminal
main_container.styles.padding = 1
search_container.styles.padding = 1
results_container.styles.padding = 1
preview_container.styles.padding = 1
preview_container.styles.min_height = 2
preview_container.styles.max_height = 3
search_container.styles.min_height = 4
search_container.styles.max_height = 6
elif width > 120 and height > 40:
# Grand terminal
main_container.styles.padding = 2
search_container.styles.padding = 2
results_container.styles.padding = 2
preview_container.styles.padding = 2
preview_container.styles.min_height = 4
preview_container.styles.max_height = 6
search_container.styles.min_height = 6
search_container.styles.max_height = 10
else:
# Terminal moyen (par défaut)
main_container.styles.padding = 1
search_container.styles.padding = 1
results_container.styles.padding = 1
preview_container.styles.padding = 1
preview_container.styles.min_height = 3
preview_container.styles.max_height = 5
search_container.styles.min_height = 5
search_container.styles.max_height = 8
# Ajuster la hauteur de la preview selon la hauteur du terminal
if height < 30:
preview_container.styles.min_height = 2
preview_container.styles.max_height = 3
elif height > 50:
preview_container.styles.min_height = 4
preview_container.styles.max_height = 6
def update_list_height(self) -> None:
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
try:
results_list = self.query_one("#results-list")
available_height = self.size.height - self.calculate_fixed_height()
# Ajuster la hauteur de la liste
if available_height > 15:
results_list.styles.height = available_height - 2
elif available_height > 10:
results_list.styles.height = available_height
else:
results_list.styles.height = 8
except Exception:
pass
def calculate_fixed_height(self) -> int:
"""Calcule la hauteur fixe des éléments (header, footer, search, preview)"""
try:
search_container = self.query_one("#search-container")
preview_container = self.query_one("#preview-container")
# Récupérer les hauteurs actuelles
search_height = search_container.styles.min_height or 5
preview_height = preview_container.styles.min_height or 3
# Estimer la hauteur fixe totale
if isinstance(search_height, (int, float)):
search_h = search_height
else:
search_h = 5
if isinstance(preview_height, (int, float)):
preview_h = preview_height
else:
preview_h = 3
# Header + Footer + margins + padding
fixed_height = 2 + search_h + preview_h + 4
return int(fixed_height)
except Exception:
return 18 # Valeur par défaut en cas d'erreur
def on_input_changed(self, event: Input.Changed) -> None:
"""Gère les changements dans le champ de recherche"""
if event.input.id == "search-input":
self.current_input = event.value
self.update_preview()
self.update_results()
def update_preview(self) -> None:
"""Met à jour l'aperçu du texte saisi"""
preview_content = self.query_one("#preview-content")
if self.current_input:
preview_text = f"📝 {self.current_input}"
# Si le texte correspond à un terme connu
matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()]
if matching_terms:
preview_text = f"{self.current_input} (terme trouvé !)"
preview_content.update(preview_text)
else:
preview_content.update("En attente de saisie...")
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Gère la soumission du champ de recherche (touche Entrée)"""
if event.input.id == "search-input":
# Sélectionner le premier résultat
results_list = self.query_one("#results-list")
if results_list.children:
first_item = results_list.children[0]
if isinstance(first_item, TermListItem):
self.toggle_term_expansion(first_item.term)
def update_results(self) -> None:
"""Met à jour la liste des résultats"""
search_text = self.current_input.lower().strip()
# Filtrer les termes
if search_text:
self.filtered_terms = [
term for term in self.terms
if search_text in term.lower()
]
else:
self.filtered_terms = self.terms.copy()
# Mettre à jour la ListView
results_list = self.query_one("#results-list")
results_list.clear()
for term in self.filtered_terms:
definition = self.dictionary[term]
# Vérifier si le terme est expansé
expanded = term in self.expanded_terms
item = TermListItem(term, definition, expanded)
results_list.append(item)
# Mettre à jour le compteur
results_label = self.query_one("#results-label")
count = len(self.filtered_terms)
if count == 0:
results_label.update("❌ Aucun résultat trouvé")
else:
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Gère la sélection d'un terme dans la liste"""
if event.item and isinstance(event.item, TermListItem):
# Basculer l'expansion du terme sélectionné
self.toggle_term_expansion(event.item.term)
def toggle_term_expansion(self, term: str) -> None:
"""Bascule l'état d'expansion d'un terme"""
if term in self.expanded_terms:
self.expanded_terms.remove(term)
else:
# Si un autre terme est expansé, le réduire
if len(self.expanded_terms) > 0:
# Réduire tous les autres termes
for other_term in list(self.expanded_terms):
if other_term != term:
self.expanded_terms.remove(other_term)
self.expanded_terms.add(term)
# Mettre à jour l'affichage
self.update_results()
# Mettre à jour le champ de recherche
search_input = self.query_one("#search-input")
search_input.value = term
self.current_input = term
self.update_preview()
def on_key(self, event: events.Key) -> None:
"""Gère les touches spéciales"""
if event.key == "escape":
# Effacer la recherche
search_input = self.query_one("#search-input")
search_input.value = ""
self.current_input = ""
self.expanded_terms.clear()
self.update_preview()
self.update_results()
search_input.focus()
elif event.key == "ctrl+f":
# Focus sur la recherche
self.query_one("#search-input").focus()
elif event.key == "enter":
# Si un item est sélectionné, basculer son expansion
results_list = self.query_one("#results-list")
if results_list.children and hasattr(results_list, 'highlighted_child'):
highlighted = results_list.highlighted_child
if highlighted and isinstance(highlighted, TermListItem):
self.toggle_term_expansion(highlighted.term)
# Point d'entrée de l'application
if __name__ == "__main__":
app = DictionaryApp()
app.run()