Files
Forge-Engine/screens/widgets/registry.py
T
william cc0442ef5f Add a Font Awesome icon gallery to the properties panel, draggable onto the screen
New "Icône" widget (<i class="...">, color/size customizable exactly like any other widget) plus a curated set of ~250 verified Font Awesome 5 Free solid icon names (fontawesome_icons.py) — not the full ~1500-icon catalog, since an embedded list needs to be guaranteed accurate (a wrong class name silently renders as a blank glyph); any other valid FA5 class still works by typing it directly into the widget's "Icône" setting.

The gallery lives in the (now always-visible) top of the right floating panel, searchable, with each tile both clickable and HTML5-draggable onto the canvas — either action posts to element_add with the chosen icon_class, which now seeds the new element's class instead of leaving it on the generic default. Available in the element-type template editor too, since it reuses screen_edit.html.

Loads Font Awesome 5.15.4 (cdnjs) alongside the existing animate.css/Bulma links, in both the editor and /play.

Verified end to end: gallery renders with real icon glyphs, clicking/simulated-drop creates a correctly-classed <i> element, and the actual /play route renders it with Font Awesome loaded. Full suite green (89).
2026-08-24 19:10:25 +02:00

335 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from .controls.c_text import c_text
from .controls.c_textarea import c_textarea
from .controls.c_url import c_url
from .controls.c_color import c_color
from .controls.c_slider import c_slider
from .controls.c_checkbox import c_checkbox
from .controls.c_toggle_token import c_toggle_token
from .controls.c_align import c_align
from .controls.c_select import c_select
from .control_groups.text_style_controls_extra import TEXT_STYLE_CONTROLS_EXTRA
from .control_groups.data_binding_controls import DATA_BINDING_CONTROLS
from .control_groups.bulma_controls import (
BULMA_BUTTON_CONTROLS, BULMA_TITLE_CONTROLS, BULMA_PROGRESS_CONTROLS,
BULMA_TABLE_CONTROLS, BULMA_TABS_CONTROLS, BULMA_FIELD_CONTROLS,
)
from .font_options import FONT_OPTIONS
from ..flow.constants import CONDITION_OPERATORS
WIDGETS = {
"texte": {
"label": "Texte", "icon": "📝", "tag": "p", "has_content": True,
"w": 40, "h": 10, "default_content": "Texte du paragraphe.",
"controls": [
c_textarea("content", "Texte — {{champ}} affiche la valeur de ce champ sur la ligne trouvée ci-dessous", "content", default="Texte du paragraphe.", group="Contenu"),
] + DATA_BINDING_CONTROLS + [
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=64, default=16, group="Texte"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
c_align("text-align", "Alignement", "style:text-align", group="Texte"),
c_checkbox("bold", "Gras", "style:font-weight", "bold", "normal", group="Texte"),
c_checkbox("italic", "Italique", "style:font-style", "italic", "normal", group="Texte"),
c_toggle_token("underline", "Souligné", "style:text-decoration", "underline", group="Texte"),
c_toggle_token("strikethrough", "Barré", "style:text-decoration", "line-through", group="Texte"),
] + TEXT_STYLE_CONTROLS_EXTRA,
},
"titre": {
"label": "Titre", "icon": "🔠", "tag": None, "has_content": True,
"w": 55, "h": 10, "default_content": "Titre",
# class:"title" — poids de police et espacement Bulma par défaut ; la
# taille reste pilotée par le réglage "Taille du texte" ci-dessous
# comme avant (non affecté), Bulma ne fournissant ici que l'habillage.
"fixed_attrs": {"class": "title"},
"controls": [
c_textarea("content", "Texte — {{champ}} affiche la valeur de ce champ sur la ligne trouvée ci-dessous", "content", default="Titre", group="Contenu"),
c_select("niveau", "Importance du titre", "attr:_niveau",
[("1", "Très grand — titre principal"), ("2", "Grand"), ("3", "Moyen")], default="2", group="Contenu"),
] + DATA_BINDING_CONTROLS + BULMA_TITLE_CONTROLS + [
c_slider("font-size", "Taille du texte", "style:font-size", min=14, max=80, default=32, group="Texte"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
c_align("text-align", "Alignement", "style:text-align", default="left", group="Texte"),
c_checkbox("italic", "Italique", "style:font-style", "italic", "normal", group="Texte"),
c_toggle_token("underline", "Souligné", "style:text-decoration", "underline", group="Texte"),
] + TEXT_STYLE_CONTROLS_EXTRA,
},
"bouton": {
"label": "Bouton", "icon": "🔘", "tag": "button", "has_content": True,
"w": 30, "h": 8, "default_content": "Valider",
# class:"button" — donne au Bouton l'apparence Bulma par défaut
# (padding, poids de police, transitions...) tant qu'aucune couleur/
# taille "libre" n'est personnalisée ci-dessous — voir
# BULMA_BUTTON_CONTROLS et _visible_attrs.py.
"fixed_attrs": {"class": "button"},
"controls": [
c_text("content", "Texte du bouton", "content", default="Valider", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#ffffff", group="Texte"),
c_color("background", "Couleur de fond", "style:background-color", "#5b8cff", group="Texte"),
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=15, group="Texte", no_freeze=True),
c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=40, default=8, group="Texte", no_freeze=True),
c_checkbox("submit", "Valide un formulaire", "attr:type", "submit", "button", group="Contenu"),
] + BULMA_BUTTON_CONTROLS + TEXT_STYLE_CONTROLS_EXTRA,
},
"lien": {
"label": "Lien", "icon": "🔗", "tag": "a", "has_content": True,
"w": 25, "h": 6, "default_content": "Cliquez ici",
"controls": [
c_text("content", "Texte du lien", "content", default="Cliquez ici", group="Contenu"),
c_url("href", "Destination", "attr:href", default="#", group="Contenu"),
c_checkbox("new_tab", "Ouvrir dans un nouvel onglet", "attr:target", "_blank", "", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#5b8cff", group="Texte"),
c_toggle_token("underline", "Souligné", "style:text-decoration", "underline", group="Texte"),
] + TEXT_STYLE_CONTROLS_EXTRA,
},
"image": {
"label": "Image", "icon": "🖼️", "tag": "img", "has_content": False, "self_closing": True,
"w": 35, "h": 22, "default_content": "",
"controls": [
c_url("src", "Source de l'image", "attr:src", allow_upload=True, upload_accept="image/*", group="Contenu"),
c_text("alt", "Texte alternatif", "attr:alt", placeholder="décrit l'image pour l'accessibilité", group="Contenu"),
c_select("object-fit", "Ajustement dans son cadre", "style:object-fit",
[("cover", "Remplir en recadrant"), ("contain", "Tout montrer sans déformer"), ("fill", "Étirer pour remplir tout le cadre")],
default="cover", group="Média"),
c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=60, default=0, group="Média"),
],
},
"video": {
"label": "Vidéo", "icon": "🎬", "tag": "video", "has_content": False,
"w": 40, "h": 25, "default_content": "",
"controls": [
c_url("src", "Source de la vidéo", "attr:src", allow_upload=True, upload_accept="video/*", group="Contenu"),
c_checkbox("controls", "Afficher les contrôles de lecture", "attr:controls", "controls", "", default=True, group="Contenu"),
c_checkbox("autoplay", "Lecture automatique", "attr:autoplay", "autoplay", "", group="Contenu"),
c_checkbox("loop", "Lecture en boucle", "attr:loop", "loop", "", group="Contenu"),
],
},
"champ_texte": {
"label": "Champ — texte", "icon": "⌨️", "tag": "input", "has_content": False, "self_closing": True,
"w": 40, "h": 8, "default_content": "", "fixed_attrs": {"type": "text", "class": "input"},
"controls": [
c_text("placeholder", "Texte indicatif", "attr:placeholder", placeholder="ex. Votre réponse", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. reponse1", group="Contenu"),
c_checkbox("required", "Obligatoire", "attr:required", "required", "", group="Contenu"),
] + BULMA_FIELD_CONTROLS,
},
"champ_email": {
"label": "Champ — email", "icon": "📧", "tag": "input", "has_content": False, "self_closing": True,
"w": 40, "h": 8, "default_content": "", "fixed_attrs": {"type": "email", "class": "input"},
"controls": [
c_text("placeholder", "Texte indicatif", "attr:placeholder", placeholder="ex. votre@email.com", default="votre@email.com", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. email", group="Contenu"),
c_checkbox("required", "Obligatoire", "attr:required", "required", "", group="Contenu"),
] + BULMA_FIELD_CONTROLS,
},
"champ_mot_de_passe": {
"label": "Champ — mot de passe", "icon": "🔒", "tag": "input", "has_content": False, "self_closing": True,
"w": 40, "h": 8, "default_content": "", "fixed_attrs": {"type": "password", "class": "input"},
"controls": [
c_text("placeholder", "Texte indicatif", "attr:placeholder", placeholder="ex. Mot de passe", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. mot_de_passe", group="Contenu"),
c_checkbox("required", "Obligatoire", "attr:required", "required", "", group="Contenu"),
] + BULMA_FIELD_CONTROLS,
},
"case_a_cocher": {
"label": "Case à cocher", "icon": "☑️", "tag": "input", "has_content": True, "special_render": "checkbox_label",
"w": 35, "h": 8, "default_content": "Option",
"controls": [
c_text("content", "Texte affiché", "content", default="Option", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. accepte_cgu", group="Contenu"),
c_text("value", "Valeur envoyée", "attr:value", placeholder="ex. oui", group="Contenu"),
c_checkbox("checked", "Coché par défaut", "attr:checked", "checked", "", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
],
},
"bouton_radio": {
"label": "Bouton radio", "icon": "🔘", "tag": "input", "has_content": True, "special_render": "checkbox_label",
"w": 35, "h": 8, "default_content": "Option",
"controls": [
c_text("content", "Texte affiché", "content", default="Option", group="Contenu"),
c_text("name", "Nom du groupe", "attr:name", placeholder="ex. choix1", group="Contenu"),
c_text("value", "Valeur envoyée", "attr:value", placeholder="ex. optionA", group="Contenu"),
c_checkbox("checked", "Sélectionné par défaut", "attr:checked", "checked", "", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
],
},
"zone_texte": {
"label": "Zone de texte", "icon": "🗒️", "tag": "textarea", "has_content": False,
"w": 40, "h": 18, "default_content": "", "fixed_attrs": {"class": "textarea"},
"controls": [
c_text("placeholder", "Texte indicatif", "attr:placeholder", placeholder="ex. Votre message", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. message", group="Contenu"),
c_checkbox("required", "Obligatoire", "attr:required", "required", "", group="Contenu"),
] + BULMA_FIELD_CONTROLS,
},
"liste_deroulante": {
"label": "Liste déroulante", "icon": "🔽", "tag": "select", "has_content": True, "special_render": "select_options",
"w": 35, "h": 8, "default_content": "Option 1\nOption 2\nOption 3",
"controls": [
c_textarea("content", "Options — une par ligne", "content", default="Option 1\nOption 2\nOption 3", group="Contenu"),
c_text("name", "Nom du champ", "attr:name", placeholder="ex. choix", group="Contenu"),
c_checkbox("required", "Obligatoire", "attr:required", "required", "", group="Contenu"),
] + BULMA_FIELD_CONTROLS,
},
"groupe_champs": {
"label": "Groupe de champs", "icon": "🗃️", "tag": "fieldset", "has_content": True, "special_render": "fieldset_legend",
"w": 55, "h": 30, "default_content": "Groupe de champs",
"controls": [
c_text("content", "Titre du groupe", "content", default="Groupe de champs", group="Contenu"),
],
},
"liste": {
"label": "Liste à puces", "icon": "📋", "tag": "ul", "has_content": True,
"w": 40, "h": 22, "default_content": "Premier élément\nDeuxième élément\nTroisième élément",
"controls": [
c_textarea("content", "Éléments de la liste — un par ligne", "content",
default="Premier élément\nDeuxième élément\nTroisième élément", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=16, group="Texte"),
],
},
"liste_numerotee": {
"label": "Liste numérotée", "icon": "🔢", "tag": "ol", "has_content": True,
"w": 40, "h": 22, "default_content": "Premier élément\nDeuxième élément\nTroisième élément",
"controls": [
c_textarea("content", "Éléments de la liste — un par ligne", "content",
default="Premier élément\nDeuxième élément\nTroisième élément", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=16, group="Texte"),
],
},
"tableau": {
"label": "Tableau", "icon": "🗂️", "tag": "table", "has_content": True, "special_render": "table",
"w": 55, "h": 25, "default_content": "Colonne 1;Colonne 2\nValeur A;Valeur B",
"fixed_attrs": {"class": "table"},
"controls": [
c_textarea("content", "Contenu — une ligne par rangée, colonnes séparées par ;", "content",
default="Colonne 1;Colonne 2\nValeur A;Valeur B", group="Contenu"),
c_checkbox("header_row", "Première ligne = en-tête", "attr:_header_row", "1", "", default=True, group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
] + BULMA_TABLE_CONTROLS,
},
"conteneur": {
"label": "Conteneur / décor", "icon": "⬛", "tag": "div", "has_content": True,
"w": 50, "h": 30, "default_content": "",
# class:"box" — ce widget EST une carte Bulma nativement (fond clair,
# ombre, coins arrondis), pas un habillage qu'on choisit dans un
# menu : voir bouton/titre ci-dessus, même logique. Une couleur de
# fond personnalisée (réglage ci-dessous) reste prioritaire dès
# qu'elle est choisie, exactement comme avant.
"fixed_attrs": {"class": "box"},
"controls": [
c_text("content", "Texte", "content", group="Contenu"),
c_color("background", "Couleur de fond", "style:background-color", "#1f2430", group="Texte"),
c_slider("border-radius", "Arrondi des coins", "style:border-radius", min=0, max=60, default=10, group="Texte", no_freeze=True),
c_slider("opacity", "Opacité", "style:opacity", unit="", min=0, max=1, step=0.05, default=1, group="Texte"),
],
},
"separateur": {
"label": "Séparateur", "icon": "➖", "tag": "hr", "has_content": False, "self_closing": True,
"w": 50, "h": 2, "default_content": "",
"controls": [
c_color("color", "Couleur", "style:border-color", "#2b3140", group="Texte"),
],
},
"repeteur_donnees": {
"label": "Répéteur de données", "icon": "🔁", "tag": "div", "has_content": True, "special_render": "repeater",
"w": 55, "h": 35, "default_content": "{{nom}}",
"controls": [
c_select("definition_id", "Objet de données à afficher", "attr:_definition_id",
[("", "Sélectionner un objet")], default="", group="Contenu"),
c_select("template_type_id", "Modèle de ligne — élément de jeu", "attr:_template_type_id",
[("", "Aucun — utiliser le modèle de texte ci-dessous")], default="", group="Contenu"),
c_textarea("content", "Modèle d'affichage texte — utilise {{nom_du_champ}}", "content", default="{{nom}}", group="Contenu"),
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=40, default=16, group="Texte"),
c_text("filtre_champ", "Filtrer : n'afficher que les lignes où ce champ",
"attr:_filtre_champ", placeholder="ex. niveau", default="", group="Filtre"),
c_select("filtre_operateur", "...est", "attr:_filtre_operateur", CONDITION_OPERATORS, default="egal", group="Filtre"),
c_text("filtre_valeur", "...cette valeur, ou {{NomObjet.champ}} pour comparer à une donnée qui change en cours de partie",
"attr:_filtre_valeur", placeholder="ex. 3 ou {{Partie.niveau_courant}}", default="", group="Filtre"),
c_text("filtre2_champ", "ET (optionnel) : ce 2ᵉ champ",
"attr:_filtre2_champ", placeholder="ex. parcour_id", default="", group="Filtre"),
c_select("filtre2_operateur", "...est", "attr:_filtre2_operateur", CONDITION_OPERATORS, default="egal", group="Filtre"),
c_text("filtre2_valeur", "...cette valeur, ou {{NomObjet.champ}}",
"attr:_filtre2_valeur", placeholder="ex. 1 ou {{Partie.parcours_courant}}", default="", group="Filtre"),
],
},
"jauge": {
"label": "Jauge — liée à une donnée", "icon": "📊", "tag": "div", "has_content": False, "special_render": "jauge",
"w": 60, "h": 12, "default_content": "",
"controls": [
# La clé "definition_id" est reconnue spécifiquement dans
# controls_with_values.py pour être peuplée avec les VRAIS
# objets de données du jeu — même mécanisme que le Répéteur.
c_select("definition_id", "Objet de données à suivre", "attr:_definition_id",
[("", "Sélectionner un objet")], default="", group="Donnée"),
# row_id/champ/champ_nom : options peuplées dynamiquement une
# fois "definition_id" choisi — voir controls_with_values.py
# (premier affichage) et onJaugeDefinitionChange() dans
# screen_edit.html (changement en direct dans le panneau).
c_select("row_id", "Enregistrement (ligne) à suivre", "attr:_row_id",
[("", "Sélectionner un objet d'abord")], default="", group="Donnée"),
c_select("champ", "Champ numérique à afficher", "attr:_champ",
[("", "Sélectionner un objet d'abord")], default="", group="Donnée"),
c_select("champ_nom", "Champ contenant le nom (affiché au-dessus, optionnel)", "attr:_champ_nom",
[("", "— aucun —")], default="", group="Donnée"),
c_text("min", "Valeur mini", "attr:_min", default="0", group="Donnée"),
c_text("max", "Valeur maxi", "attr:_max", default="100", group="Donnée"),
c_color("couleur_basse", "Couleur côté mini", "attr:_couleur_basse", "#e05252", group="Apparence"),
c_color("couleur_haute", "Couleur côté maxi", "attr:_couleur_haute", "#4caf50", group="Apparence"),
c_checkbox("afficher_valeur", "Afficher la valeur en chiffres sur la jauge", "attr:_afficher_valeur", "1", "", default=False, group="Apparence"),
c_select("nom_position", "Position du nom", "attr:_nom_position",
[("dessus", "Au-dessus de la jauge"), ("cote", "À côté (à gauche) de la jauge")],
default="dessus", group="Apparence"),
c_select("nom_alignement", "Alignement du nom (si au-dessus)", "attr:_nom_alignement",
[("centre", "Centré"), ("gauche", "Aligné à gauche")],
default="centre", group="Apparence"),
c_select("nom_police", "Police du nom", "attr:_nom_police", FONT_OPTIONS, default="inherit", group="Apparence"),
c_slider("nom_taille", "Taille du texte du nom", "attr:_nom_taille", unit="", min=8, max=32, default=12, group="Apparence"),
c_checkbox("nom_gras", "Nom en gras", "attr:_nom_gras", "1", "", default=False, group="Apparence"),
c_checkbox("nom_italique", "Nom en italique", "attr:_nom_italique", "1", "", default=False, group="Apparence"),
] + BULMA_PROGRESS_CONTROLS,
},
"superposition": {
# 3.4 (Confort) — overlay/modale réutilisable : couvre TOUJOURS tout
# l'écran (position:fixed, indépendant de x/y/width/height — voir
# render_overlay.py), avec un voile semi-transparent + une boîte
# centrée qui contient les éléments posés à l'intérieur. Fermeture
# manuelle uniquement : réutilise l'action existante "Modifier un
# élément → Visibilité" (masquer/rendre visible), comme n'importe
# quel autre élément — pas de mécanisme de fermeture au clic externe.
"label": "Superposition / boîte de dialogue", "icon": "🪟", "tag": "div", "has_content": True,
"special_render": "overlay",
"w": 50, "h": 40, "default_content": "",
"controls": [
c_text("content", "Texte — en plus des éléments posés dans la boîte", "content", group="Contenu"),
c_color("couleur_boite", "Couleur de fond de la boîte", "attr:_couleur_boite", "#1f2430", group="Apparence"),
c_slider("arrondi", "Arrondi des coins de la boîte", "attr:_arrondi", unit="", min=0, max=40, default=12, group="Apparence"),
],
},
"onglets": {
# Chaque onglet est un vrai élément "conteneur" posé comme enfant
# (voir screens/elements/add_tab.py et render_onglets.py) — pas de
# gestion dédiée ici : ajouter/renommer/supprimer un onglet se fait
# via le bloc "Onglets" du panneau de propriétés (screen_edit.html),
# qui réutilise les routes existantes (element_rename, element_delete).
"label": "Onglets", "icon": "🗂️", "tag": "div", "has_content": False, "special_render": "onglets",
"w": 60, "h": 40, "default_content": "",
"controls": BULMA_TABS_CONTROLS,
},
"icone": {
# La classe Font Awesome ("fa-solid fa-xxx") EST le contenu visuel de
# ce widget — posée soit en cliquant/glissant une icône depuis la
# galerie du panneau de propriétés (voir routes/elements/element_add.py,
# qui pré-remplit ce réglage), soit tapée à la main ici pour n'importe
# quelle icône Font Awesome 5 hors de la sélection curatée de la
# galerie (voir fontawesome_icons.py).
"label": "Icône", "icon": "🔤", "tag": "i", "has_content": False,
"w": 15, "h": 15, "default_content": "",
"controls": [
c_text("icon_class", "Icône (classe Font Awesome 5)", "attr:class",
default="fa-solid fa-star", placeholder="fa-solid fa-heart", group="Contenu"),
c_slider("font-size", "Taille", "style:font-size", min=10, max=160, default=32, group="Texte"),
c_color("color", "Couleur", "style:color", "#e8eaf0", group="Texte"),
],
},
}