# toaster — `n_pbt_toaster` #

← Component reference · Guide contents

> "Toast" notifications in a screen corner: a message that appears, informs and disappears without blocking the user or interrupting their typing.

> ▶ See it live — Demo application, Toaster tile: the preview, the code behind it and this page, side by side.

## At a glance #

Object | `n_pbt_toaster` — non-visual: nothing to place on the window
Used for | Confirming a successful action, reporting a warning or an error, without stopping the work in progress
Return | Non-blocking: `of_show()` returns immediately; user reactions come back through events

The toast is a detached window: it floats above your application (or above the whole screen) and closes on its own.

    n_pbt_toaster lnv_toast

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

## Quick start #

    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster

    lnv_toast.ipo_owner = this                       // window the toast attaches to
    lnv_toast.is_kind   = lnv_toast.KIND_SUCCESS     // green icon + success stripe
    lnv_toast.is_text   = "Your changes have been [b]saved[/b]."
    lnv_toast.of_show()

    destroy lnv_toast

Everything is configured through properties, then `of_show()` — which takes no argument — makes the notification appear.

## Properties #

To be set before `of_show`.

Property | Type | Default | Purpose
--- | --- | --- | ---
`is_text` | string | `""` | Body of the message. Accepts rich text markup
`is_kind` | string | `KIND_INFO` | Level: drives the icon and the color of the stripe. `KIND_*` constants
`is_position` | string | `POSITION_BOTTOM_RIGHT` | Anchor corner. `POSITION_*` constants
`ib_screen` | boolean | `false` | `false` = anchored to the corner of the window; `true` = anchored to the corner of the screen, floating above everything
`il_timeout` | long | `TIMEOUT_AUTO` | Display time in milliseconds before the toast closes on its own. `TIMEOUT_AUTO` (`-1`, the default) means 4 s for an information but until it is clicked for an error — an error that vanishes after four seconds is an error lost. `TIMEOUT_UNTIL_CLICKED` (`0`) makes any toast persistent; an explicit duration is honoured as-is. While the pointer rests on a toast its countdown is suspended, and a bar shows the time left
`is_title` | string | `""` | Bold title line above the message (rich toast)
`is_image` | string | `""` | Illustration image on the left, in place of the level icon (accepted forms: path, `mono:`, DLL resource)
`is_key` | string | `""` | Key of the toast: showing the same key again updates the toast already on screen instead of opening a second one. That is what a progress notification needs ("Export 3/10" then "4/10"): closing and reopening would restart the animation and shuffle the stack. Leave it empty for an ordinary toast
`il_max_visible` | long | `5` | Maximum number of toasts on screen at the same corner (1 to 20). Past that, the next ones wait and appear as room frees up: a batch loop firing one toast per row used to stack windows straight off the screen
`ipo_owner` | powerobject | — | The visual object the toast is attached to (its window corner serves as the reference point)
`ipo_receiver` | powerobject | — | The visual object that receives the events of the toast. Leave it empty for a notification without feedback

### Constants #

Constant | Value | Use
--- | --- | ---
`KIND_INFO` | `"info"` | Neutral information
`KIND_SUCCESS` | `"success"` | Successful operation
`KIND_WARNING` | `"warning"` | Warning
`KIND_ERROR` | `"error"` | Failure
`POSITION_TOP_LEFT` | `"top-left"` | Top left corner
`POSITION_TOP_CENTER` | `"top-center"` | Top, centered
`POSITION_TOP_RIGHT` | `"top-right"` | Top right corner
`POSITION_BOTTOM_LEFT` | `"bottom-left"` | Bottom left corner
`POSITION_BOTTOM_CENTER` | `"bottom-center"` | Bottom, centered
`POSITION_BOTTOM_RIGHT` | `"bottom-right"` | Bottom right corner (default)

> Why `LEFT`/`RIGHT` here, and `START`/`END` elsewhere? The toast is a system window placed in screen pixels, not content that follows a reading direction: a screen corner has no "beginning". These constants therefore deliberately remain physical, and do not switch sides when the application moves to right-to-left writing. See Language and RTL.

## Methods #

Method | Purpose
--- | ---
`of_show ( )` → long | Displays the notification built from the properties. Returns the identifier of the toast (`> 0`), or a negative value on error. Does not block
`of_close ( long al_id )` → long | Closes a toast still on screen, designated by the identifier returned by `of_show`. Returns `0`, or `-5` if the id is empty or names a toast that has already disappeared
`of_reset ( )` | Resets every content property to its default and clears the buttons (`ipo_owner` and `ipo_receiver` are kept: they are wiring, not content)
`of_process_events ( )` | Empties the queue of toast feedback and raises the matching `ue_toast_*` events — see below
`of_add_button (string as_key, string as_label {, string as_image })` → long | Adds an action button (up to 3) and returns how many there are. A click on it raises `ue_toast_action(id, key)`; `of_reset` clears the buttons. A label may contain any character — a comma, an equals sign — without being cut

Several toasts displayed in the same corner stack automatically. Each one carries a close cross; the identifier returned by `of_show` lets you tell them apart in the events and close them from your code.

The countdown pauses while the pointer is over the toast: a notification must not vanish under the eyes of whoever is reading it. A thin bar at the bottom of the toast shows the time left — and therefore explains its disappearance. A click on the body answers and closes, in both hosting modes.

A notification set with `il_timeout = 0` stays on screen as long as nobody closes it: keep its identifier so you can remove it once the task it announces is finished.

    long ll_toast

    // Persistent notification : it will stay on screen until of_close.
    inv_toaster.is_text = "Export in progress..."
    inv_toaster.il_timeout = /*ms, 0 = no auto close*/ 0
    ll_toast = inv_toaster.of_show()

    // ... long processing ...

    inv_toaster.of_close(/*id*/ ll_toast)

## Events — the toast talks back #

A notification is not a plain "show and forget": it can tell you that it was clicked, that an action button was chosen, or that it closed.

Because the toast lives in a detached window, the wiring takes three steps.

1. Designate the target visual object:

    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this      // this userobject / this window will receive the feedback

> `ipo_receiver` must be a visual object (window or userobject). A non-visual object cannot receive a system notification.

2. Declare on that visual object an event mapped to `pbm_custom02`, which empties the queue:

    // ue_toast_notified event, mapped to pbm_custom02
    if IsValid(inv_toaster) then inv_toaster.of_process_events()

3. Handle the events raised on the toaster:

Event | Raised when
--- | ---
`ue_toast_clicked (string as_id)` | The body of the toast is clicked (not a button)
`ue_toast_action (string as_id, string as_action)` | An action button is clicked; `as_action` holds the key passed to `of_add_button`
`ue_toast_dismissed (string as_id)` | The toast closes: timeout elapsed, close cross, or after an action

Without `ipo_receiver`, the notification appears and disappears without ever sending anything back — that is the simplest mode, perfect for a plain confirmation.

## Examples #

### The four levels #

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

    inv_toaster.of_reset()
    inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
    inv_toaster.is_text = "Your changes have been [b]saved[/b]."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind    = inv_toaster.KIND_WARNING
    inv_toaster.il_timeout = 5000                        // a little longer
    inv_toaster.is_text    = "Low disk space on drive 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    = "Unable to reach the server."
    inv_toaster.of_show()

### Choosing the corner, in the window or on the screen #

    // Anchored to the top right corner of the WINDOW (default : follows the application)
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = false
    inv_toaster.is_text     = "Anchored to the window corner."
    inv_toaster.of_show()

    // Detached : anchored to the SCREEN corner, visible even if the window is minimized
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = true
    inv_toaster.is_text     = "Overnight processing complete."
    inv_toaster.of_show()

### Rich toast: title, image and duration #

    inv_toaster.of_reset()

    inv_toaster.is_title    = "Backup complete"
    inv_toaster.is_text     = "1,240 files copied to [b]\\server\backup[/b]."
    inv_toaster.is_image    = "mono:img\backup.svg"
    inv_toaster.il_timeout  = 8000                    // stays 8 seconds
    inv_toaster.of_show()

### Notification with action buttons #

    // open event : wire the feedback once and for all
    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this

    // Offer two actions ; timeout 0 = the toast waits for the user decision
    inv_toaster.of_reset()

    inv_toaster.is_title   = "Update available"
    inv_toaster.is_text    = "Version 2.0 is ready to install."
    inv_toaster.il_timeout = 0
    inv_toaster.of_add_button(/*key*/ "installer", /*label*/ "Install")
    inv_toaster.of_add_button(/*key*/ "plus_tard", /*label*/ "Later")
    inv_toaster.of_show()

    // ue_toast_notified event of the window, mapped to pbm_custom02
    if IsValid(inv_toaster) then inv_toaster.of_process_events()

    // ue_toast_action event of 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

### Reacting to a click on the message #

    // ue_toast_clicked event of inv_toaster : (string as_id)
    // The user clicked the body of the toast : open the relevant screen
    Open(w_journal_import)

### Notifying from a long process #

    // End of an import : inform without blocking the data entry screen
    inv_toaster.of_reset()

    if ll_erreurs = 0 then
        inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
        inv_toaster.is_text = "Import complete : [b]" + String(ll_lignes) + " rows[/b] loaded."
    else
        inv_toaster.is_kind    = inv_toaster.KIND_ERROR
        inv_toaster.il_timeout = 0                    // an error must be read
        inv_toaster.is_text    = "Import interrupted : " + String(ll_erreurs) + " errors."
    end if

    inv_toaster.of_show()

## Best practices #

- One persistent instance per window (an instance variable created on opening) rather than creating and destroying one for each message: the `ipo_receiver` wiring stays in place and the feedback arrives.
- Call `of_reset()` before each notification: without it, the title, the image or the buttons of the previous one stay in place.
- Reserve `il_timeout = 0` for messages that demand a decision (blocking error, offered action): a toast that never leaves on its own ends up being annoying.
- Use `ib_screen = true` only for what must stay visible when the application is in the background (end of a long process, overnight job).
- A toast is a transient message: if you absolutely need an answer before continuing, use messagebox, which blocks and returns the choice.
- For a permanent status rather than a notification, prefer statusbar.

← Component reference · Guide contents