docs(crepe-carottage): essai d'ui avec une variable et sa description
This commit is contained in:
parent
628f3fb7ca
commit
1fe0875a5a
3 changed files with 1317 additions and 0 deletions
468
doc/crepe.py
Normal file
468
doc/crepe.py
Normal 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
389
doc/crepe.py.ori
Normal 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
460
doc/crepe3.py
Normal 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()
|
||||||
Loading…
Reference in a new issue