# toaster — `n_pbt_toaster` #

← Référence des composants · Sommaire du guide

> Notifications « toast » en coin d'écran : un message qui apparaît, informe et disparaît sans bloquer l'utilisateur ni interrompre sa saisie.

> ▶ Le voir en vrai — Application de démonstration, tuile Toaster : l'aperçu, le code qui le produit et cette page, côte à côte.

## En bref #

Objet | `n_pbt_toaster` — non visuel : rien à poser dans la fenêtre
Sert à | Confirmer une action réussie, signaler un avertissement ou une erreur, sans arrêter le travail en cours
Retour | Non bloquant : `of_show()` rend la main immédiatement ; les réactions de l'utilisateur reviennent par événements

Le toast est une fenêtre détachée : il flotte au-dessus de votre application (ou de tout l'écran) et se referme tout seul.

    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster
    // ... configuration ...
    destroy lnv_toast

## Démarrage rapide #

    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster

    lnv_toast.ipo_owner = this                       // fenetre a laquelle le toast s'accroche
    lnv_toast.is_kind   = lnv_toast.KIND_SUCCESS     // icone verte + lisere de succes
    lnv_toast.is_text   = "Vos modifications ont ete [b]enregistrees[/b]."
    lnv_toast.of_show()

    destroy lnv_toast

Tout se configure par propriétés, puis `of_show()` — qui ne prend aucun argument — fait apparaître la notification.

## Propriétés #

À poser avant `of_show`.

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`is_text` | string | `""` | Corps du message. Accepte le balisage riche
`is_kind` | string | `KIND_INFO` | Niveau : pilote l'icône et la couleur du liseré. Constantes `KIND_*`
`is_position` | string | `POSITION_BOTTOM_RIGHT` | Coin d'ancrage. Constantes `POSITION_*`
`ib_screen` | boolean | `false` | `false` = ancré au coin de la fenêtre ; `true` = ancré au coin de l'écran, flottant au-dessus de tout
`il_timeout` | long | `TIMEOUT_AUTO` | Durée d'affichage en millisecondes avant fermeture automatique. `TIMEOUT_AUTO` (`-1`, le défaut) vaut 4 s pour une information mais jusqu'au clic pour une erreur — une erreur qui s'efface en quatre secondes est une erreur perdue. `TIMEOUT_UNTIL_CLICKED` (`0`) rend n'importe quel toast persistant ; une durée explicite est respectée telle quelle. Tant que le pointeur reste sur un toast, son compte à rebours est suspendu, et une barre montre le temps restant
`is_title` | string | `""` | Ligne de titre en gras au-dessus du message (toast enrichi)
`is_image` | string | `""` | Image d'illustration à gauche, à la place de l'icône de niveau (formes acceptées : chemin, `mono:`, ressource de DLL)
`is_key` | string | `""` | Clé du toast : réafficher la même clé met à jour le toast déjà à l'écran au lieu d'en ouvrir un second. C'est ce dont une notification de progression a besoin (« Export 3/10 » puis « 4/10 ») : fermer et rouvrir relancerait l'animation et ferait sauter la pile. Laissez vide pour un toast ordinaire
`il_max_visible` | long | `5` | Nombre maximal de toasts à l'écran au même coin (1 à 20). Au-delà, les suivants attendent et apparaissent à mesure que la place se libère : une boucle de traitement qui émet un toast par ligne empilait sinon les fenêtres hors de l'écran
`ipo_owner` | powerobject | — | L'objet visuel auquel le toast est accroché (son coin de fenêtre sert de repère)
`ipo_receiver` | powerobject | — | L'objet visuel qui reçoit les événements du toast. Laissez-le vide pour une notification sans retour

### Constantes #

Constante | Valeur | Usage
--- | --- | ---
`KIND_INFO` | `"info"` | Information neutre
`KIND_SUCCESS` | `"success"` | Opération réussie
`KIND_WARNING` | `"warning"` | Avertissement
`KIND_ERROR` | `"error"` | Échec
`POSITION_TOP_LEFT` | `"top-left"` | Coin haut gauche
`POSITION_TOP_CENTER` | `"top-center"` | Haut, centré
`POSITION_TOP_RIGHT` | `"top-right"` | Coin haut droit
`POSITION_BOTTOM_LEFT` | `"bottom-left"` | Coin bas gauche
`POSITION_BOTTOM_CENTER` | `"bottom-center"` | Bas, centré
`POSITION_BOTTOM_RIGHT` | `"bottom-right"` | Coin bas droit (défaut)

> Pourquoi `LEFT`/`RIGHT` ici, et `START`/`END` ailleurs ? Le toast est une fenêtre système posée en pixels écran, pas un contenu qui suit un sens de lecture : un coin d'écran n'a pas de « début ». Ces constantes restent donc volontairement physiques, et ne changent pas de côté quand l'application passe en écriture de droite à gauche. Voir Langue et RTL.

## Méthodes #

Méthode | Rôle
--- | ---
`of_show ( )` → long | Affiche la notification construite à partir des propriétés. Renvoie l'identifiant du toast (`> 0`), ou une valeur négative en cas d'erreur. Ne bloque pas
`of_close ( long al_id )` → long | Ferme un toast encore affiché, désigné par l'identifiant rendu par `of_show`. Renvoie `0`, ou `-5` si l’identifiant est vide ou désigne un toast déjà disparu
`of_reset ( )` | Remet toutes les propriétés de contenu à leur défaut et efface les boutons (`ipo_owner` et `ipo_receiver` sont conservés : ce sont des branchements, pas du contenu)
`of_process_events ( )` | Vide la file des retours du toast et lève les événements `ue_toast_*` correspondants — voir ci-dessous
`of_add_button (string as_cle, string as_libelle {, string as_image })` → long | Ajoute un bouton d'action (3 au plus) et renvoie leur nombre. Un clic dessus émet `ue_toast_action(id, cle)` ; `of_reset` efface les boutons. Un libellé peut contenir n'importe quel caractère — une virgule, un signe égal — sans être coupé

Plusieurs toasts affichés au même coin s'empilent automatiquement. Chacun porte une croix de fermeture ; l'identifiant renvoyé par `of_show` permet de les distinguer dans les événements et de les fermer depuis votre code.

Le compte à rebours se met en pause tant que le pointeur est sur le toast : une notification ne doit pas s'évanouir sous les yeux de qui la lit. Une fine barre en bas du toast montre le temps restant — et explique donc sa disparition. Un clic sur le corps répond et ferme, dans les deux modes d'hébergement.

Une notification posée avec `il_timeout = 0` reste à l'écran tant que personne ne la ferme : gardez son identifiant pour pouvoir la retirer quand la tâche qu'elle annonce est terminée.

    long ll_toast

    // Notification persistante : elle restera affichee jusqu'a of_close.
    inv_toaster.is_text = "Export en cours..."
    inv_toaster.il_timeout = /*ms, 0 = pas de fermeture auto*/ 0
    ll_toast = inv_toaster.of_show()

    // ... traitement long ...

    inv_toaster.of_close(/*id*/ ll_toast)

## Événements — le toast vous répond #

Une notification n'est pas un simple « affiche et oublie » : elle peut vous dire qu'elle a été cliquée, qu'un bouton d'action a été choisi, ou qu'elle s'est fermée.

Comme le toast vit dans une fenêtre détachée, le branchement se fait en trois étapes.

1. Désigner l'objet visuel destinataire :

    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this      // ce userobject / cette fenetre recevra les retours

> `ipo_receiver` doit être un objet visuel (fenêtre ou userobject). Un objet non visuel ne peut pas recevoir de notification système.

2. Déclarer sur cet objet visuel un event mappé sur `pbm_custom02`, qui vide la file :

    // event ue_toast_notified, mappe sur pbm_custom02
    if IsValid(inv_toaster) then inv_toaster.of_process_events()

3. Traiter les événements levés sur le toaster :

Événement | Déclenché quand
--- | ---
`ue_toast_clicked (string as_id)` | Le corps du toast est cliqué (pas un bouton)
`ue_toast_action (string as_id, string as_action)` | Un bouton d'action est cliqué ; `as_action` vaut la clé passée à `of_add_button`
`ue_toast_dismissed (string as_id)` | Le toast se ferme : délai écoulé, croix de fermeture, ou après une action

Sans `ipo_receiver`, la notification s'affiche et disparaît sans jamais rien renvoyer — c'est le mode le plus simple, parfait pour une simple confirmation.

## Exemples #

### Les quatre niveaux #

    inv_toaster.of_reset()
    inv_toaster.is_kind = inv_toaster.KIND_INFO
    inv_toaster.is_text = "Operation terminee."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
    inv_toaster.is_text = "Vos modifications ont ete [b]enregistrees[/b]."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind    = inv_toaster.KIND_WARNING
    inv_toaster.il_timeout = 5000                        // un peu plus long
    inv_toaster.is_text    = "Espace disque faible sur le lecteur C:."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind    = inv_toaster.KIND_ERROR
    inv_toaster.il_timeout = 6000
    inv_toaster.is_text    = "Impossible de joindre le serveur."
    inv_toaster.of_show()

### Choisir le coin, dans la fenêtre ou sur l'écran #

    // Ancre au coin haut droit de la FENETRE (defaut : suit l'application)
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = false
    inv_toaster.is_text     = "Ancre au coin de la fenetre."
    inv_toaster.of_show()

    // Detache : ancre au coin de l'ECRAN, visible meme si la fenetre est reduite
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = true
    inv_toaster.is_text     = "Traitement de nuit termine."
    inv_toaster.of_show()

### Toast enrichi : titre, image et durée #

    inv_toaster.of_reset()

    inv_toaster.is_title    = "Sauvegarde terminee"
    inv_toaster.is_text     = "1 240 fichiers copies vers [b]\\serveur\backup[/b]."
    inv_toaster.is_image    = "mono:img\backup.svg"
    inv_toaster.il_timeout  = 8000                    // reste 8 secondes
    inv_toaster.of_show()

### Notification avec boutons d'action #

    // event open : brancher le retour une fois pour toutes
    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this

    // Proposer deux actions ; timeout 0 = le toast attend la decision de l'utilisateur
    inv_toaster.of_reset()

    inv_toaster.is_title   = "Mise a jour disponible"
    inv_toaster.is_text    = "La version 2.0 est prete a etre installee."
    inv_toaster.il_timeout = 0
    inv_toaster.of_add_button(/*cle*/ "installer", /*libelle*/ "Installer")
    inv_toaster.of_add_button(/*cle*/ "plus_tard", /*libelle*/ "Plus tard")
    inv_toaster.of_show()

    // event ue_toast_notified de la fenetre, mappe sur pbm_custom02
    if IsValid(inv_toaster) then inv_toaster.of_process_events()

    // event ue_toast_action de inv_toaster : (string as_id, string as_action)
    choose case as_action
        case "installer" ; of_lancer_mise_a_jour()
        case "plus_tard" ; of_reporter(1)
    end choose

### Réagir au clic sur le message #

    // event ue_toast_clicked de inv_toaster : (string as_id)
    // L'utilisateur a clique le corps du toast : ouvrir l'ecran concerne
    Open(w_journal_import)

### Notifier depuis un traitement long #

    // Fin d'un import : informer sans bloquer l'ecran de saisie
    inv_toaster.of_reset()

    if ll_erreurs = 0 then
        inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
        inv_toaster.is_text = "Import termine : [b]" + String(ll_lignes) + " lignes[/b] integrees."
    else
        inv_toaster.is_kind    = inv_toaster.KIND_ERROR
        inv_toaster.il_timeout = 0                    // une erreur doit etre lue
        inv_toaster.is_text    = "Import interrompu : " + String(ll_erreurs) + " erreurs."
    end if

    inv_toaster.of_show()

## Bonnes pratiques #

- Une instance persistante par fenêtre (variable d'instance créée à l'ouverture) plutôt qu'une création/destruction à chaque message : le branchement `ipo_receiver` reste en place et les retours arrivent.
- Appelez `of_reset()` avant chaque notification : sans cela le titre, l'image ou les boutons de la précédente restent posés.
- Réservez `il_timeout = 0` aux messages qui exigent une décision (erreur bloquante, action proposée) : un toast qui ne part pas tout seul finit par agacer.
- Utilisez `ib_screen = true` uniquement pour ce qui doit rester visible quand l'application est en arrière-plan (fin de traitement long, tâche de nuit).
- Un toast est un message transitoire : s'il faut absolument une réponse avant de continuer, utilisez messagebox, qui bloque et renvoie le choix.
- Pour un statut permanent plutôt qu'une notification, préférez statusbar.

← Référence des composants · Sommaire du guide