# toaster — `n_pbt_toaster` #

← Referência dos componentes · Índice do guia

> Notificações «toast» no canto do ecrã: uma mensagem que aparece, informa e desaparece sem bloquear o utilizador nem interromper a sua introdução de dados.

> ▶ Ver ao vivo — Aplicação de demonstração, mosaico Toaster: a pré-visualização, o código que o produz e esta página, lado a lado.

## Em resumo #

Objeto | `n_pbt_toaster` — não visual: nada a colocar na window
Serve para | Confirmar uma ação bem-sucedida, assinalar um aviso ou um erro, sem parar o trabalho em curso
Retorno | Não bloqueante: `of_show()` devolve o controlo imediatamente; as reações do utilizador regressam por eventos

O toast é uma window destacada: flutua por cima da aplicação (ou de todo o ecrã) e fecha-se sozinho.

    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster
    // ... configuracao ...
    destroy lnv_toast

## Início rápido #

    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster

    lnv_toast.ipo_owner = this                       // window a qual o toast se fixa
    lnv_toast.is_kind   = lnv_toast.KIND_SUCCESS     // icone verde + friso de sucesso
    lnv_toast.is_text   = "As suas alterações foram [b]guardadas[/b]."
    lnv_toast.of_show()

    destroy lnv_toast

Tudo se configura por propriedades e, em seguida, `of_show()` — que não recebe qualquer argumento — faz aparecer a notificação.

## Propriedades #

A definir antes de `of_show`.

Propriedade | Tipo | Predefinição | Função
--- | --- | --- | ---
`is_text` | string | `""` | Corpo da mensagem. Aceita o texto formatado com etiquetas
`is_kind` | string | `KIND_INFO` | Nível: comanda o ícone e a cor do friso. Constantes `KIND_*`
`is_position` | string | `POSITION_BOTTOM_RIGHT` | Canto de fixação. Constantes `POSITION_*`
`ib_screen` | boolean | `false` | `false` = fixado ao canto da window; `true` = fixado ao canto do ecrã, flutuando por cima de tudo
`il_timeout` | long | `TIMEOUT_AUTO` | Tempo de apresentação em milissegundos antes do fecho automático. `TIMEOUT_AUTO` (`-1`, a predefinição) vale 4 s para uma informação mas até ao clique para um erro — um erro que se apaga em quatro segundos é um erro perdido. `TIMEOUT_UNTIL_CLICKED` (`0`) torna persistente qualquer toast; uma duração explícita é respeitada tal como está. Enquanto o ponteiro estiver sobre um toast a sua contagem decrescente fica suspensa, e uma barra mostra o tempo restante
`is_title` | string | `""` | Linha de título a negrito por cima da mensagem (toast enriquecido)
`is_image` | string | `""` | Imagem de ilustração à esquerda, em vez do ícone de nível (formas aceites: caminho, `mono:`, recurso de DLL)
`is_key` | string | `""` | Chave do toast: mostrar de novo a mesma chave atualiza o toast já no ecrã em vez de abrir um segundo. É disso que precisa uma notificação de progresso («Exportação 3/10» e depois «4/10»): fechar e reabrir reiniciaria a animação e baralharia a pilha. Deixe vazia para um toast comum
`il_max_visible` | long | `5` | Número máximo de toasts no ecrã no mesmo canto (1 a 20). Para além disso, os seguintes esperam e aparecem à medida que se liberta espaço: um ciclo de processamento que emite um toast por linha empilhava de outro modo as janelas para fora do ecrã
`ipo_owner` | powerobject | — | O objeto visual ao qual o toast está fixado (o canto da respetiva window serve de referência)
`ipo_receiver` | powerobject | — | O objeto visual que recebe os eventos do toast. Deve deixar-se vazio para uma notificação sem retorno

### Constantes #

Constante | Valor | Utilização
--- | --- | ---
`KIND_INFO` | `"info"` | Informação neutra
`KIND_SUCCESS` | `"success"` | Operação bem-sucedida
`KIND_WARNING` | `"warning"` | Aviso
`KIND_ERROR` | `"error"` | Falha
`POSITION_TOP_LEFT` | `"top-left"` | Canto superior esquerdo
`POSITION_TOP_CENTER` | `"top-center"` | Superior, centrado
`POSITION_TOP_RIGHT` | `"top-right"` | Canto superior direito
`POSITION_BOTTOM_LEFT` | `"bottom-left"` | Canto inferior esquerdo
`POSITION_BOTTOM_CENTER` | `"bottom-center"` | Inferior, centrado
`POSITION_BOTTOM_RIGHT` | `"bottom-right"` | Canto inferior direito (predefinição)

> Porquê `LEFT`/`RIGHT` aqui e `START`/`END` noutros locais? O toast é uma window de sistema posicionada em pixels do ecrã, não um conteúdo que siga um sentido de leitura: um canto do ecrã não tem «início». Estas constantes permanecem, portanto, deliberadamente físicas, e não mudam de lado quando a aplicação passa a escrita da direita para a esquerda. Ver Idioma e RTL.

## Métodos #

Método | Função
--- | ---
`of_show ( )` → long | Apresenta a notificação construída a partir das propriedades. Devolve o identificador do toast (`> 0`), ou um valor negativo em caso de erro. Não bloqueia
`of_close ( long al_id )` → long | Fecha um toast ainda visível, designado pelo identificador devolvido por `of_show`. Devolve `0`, ou `-5` se o identificador estiver vazio ou designar um toast que já desapareceu
`of_reset ( )` | Repõe todas as propriedades de conteúdo na predefinição e apaga os botões (`ipo_owner` e `ipo_receiver` são preservados: são ligações, não conteúdo)
`of_process_events ( )` | Esvazia a fila de retornos do toast e desencadeia os eventos `ue_toast_*` correspondentes — ver abaixo
`of_add_button (string as_key, string as_label {, string as_image })` → long | Acrescenta um botão de ação (no máximo 3) e devolve quantos existem. Um clique emite `ue_toast_action(id, chave)`; `of_reset` limpa os botões. Um rótulo pode conter qualquer carácter — uma vírgula, um sinal de igual — sem ser cortado

Vários toasts apresentados no mesmo canto empilham-se automaticamente. Cada um tem uma cruz de fecho; o identificador devolvido por `of_show` permite distingui-los nos eventos e fechá-los a partir do código.

A contagem decrescente é suspensa enquanto o ponteiro estiver sobre o toast: uma notificação não deve desvanecer-se sob os olhos de quem a lê. Uma barra fina em baixo mostra o tempo restante — e explica assim o seu desaparecimento. Um clique no corpo responde e fecha, em ambos os modos de alojamento.

Uma notificação criada com `il_timeout = 0` permanece no ecrã enquanto ninguém a fechar: convém guardar o respetivo identificador para a poder retirar quando a tarefa que anuncia estiver concluída.

    long ll_toast

    // Notificacao persistente : ficara visivel ate of_close.
    inv_toaster.is_text = "Exportação em curso..."
    inv_toaster.il_timeout = /*ms, 0 = sem fecho automatico*/ 0
    ll_toast = inv_toaster.of_show()

    // ... processamento demorado ...

    inv_toaster.of_close(/*id*/ ll_toast)

## Eventos — o toast responde #

Uma notificação não é um simples «apresentar e esquecer»: pode indicar que foi clicada, que um botão de ação foi escolhido, ou que se fechou.

Como o toast vive numa window destacada, a ligação faz-se em três etapas.

1. Designar o objeto visual destinatário:

    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this      // este userobject / esta window recebera os retornos

> `ipo_receiver` tem de ser um objeto visual (window ou userobject). Um objeto não visual não pode receber uma notificação de sistema.

2. Declarar nesse objeto visual um event mapeado em `pbm_custom02`, que esvazia a fila:

    // event ue_toast_notified, mapeado em pbm_custom02
    if IsValid(inv_toaster) then inv_toaster.of_process_events()

3. Tratar os eventos desencadeados no toaster:

Evento | Acionado quando
--- | ---
`ue_toast_clicked (string as_id)` | O corpo do toast é clicado (não um botão)
`ue_toast_action (string as_id, string as_action)` | Um botão de ação é clicado; `as_action` contém a chave passada a `of_add_button`
`ue_toast_dismissed (string as_id)` | O toast fecha-se: prazo esgotado, cruz de fecho, ou após uma ação

Sem `ipo_receiver`, a notificação aparece e desaparece sem nunca devolver nada — é o modo mais simples, perfeito para uma simples confirmação.

## Exemplos #

### Os quatro níveis #

    inv_toaster.of_reset()
    inv_toaster.is_kind = inv_toaster.KIND_INFO
    inv_toaster.is_text = "Operação concluída."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
    inv_toaster.is_text = "As suas alterações foram [b]guardadas[/b]."
    inv_toaster.of_show()

    inv_toaster.of_reset()
    inv_toaster.is_kind    = inv_toaster.KIND_WARNING
    inv_toaster.il_timeout = 5000                        // um pouco mais longo
    inv_toaster.is_text    = "Espaço em disco reduzido na unidade 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    = "Não foi possível contactar o servidor."
    inv_toaster.of_show()

### Escolher o canto, na window ou no ecrã #

    // Fixado ao canto superior direito da WINDOW (predefinicao : acompanha a aplicacao)
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = false
    inv_toaster.is_text     = "Fixado ao canto da window."
    inv_toaster.of_show()

    // Destacado : fixado ao canto do ECRA, visivel mesmo se a window estiver minimizada
    inv_toaster.is_position = inv_toaster.POSITION_TOP_RIGHT
    inv_toaster.ib_screen   = true
    inv_toaster.is_text     = "Processamento noturno concluído."
    inv_toaster.of_show()

### Toast enriquecido: título, imagem e duração #

    inv_toaster.of_reset()

    inv_toaster.is_title    = "Cópia de segurança concluída"
    inv_toaster.is_text     = "1 240 ficheiros copiados para [b]\\servidor\backup[/b]."
    inv_toaster.is_image    = "mono:img\backup.svg"
    inv_toaster.il_timeout  = 8000                    // permanece 8 segundos
    inv_toaster.of_show()

### Notificação com botões de ação #

    // event open : ligar o retorno de uma vez por todas
    inv_toaster.ipo_owner    = this
    inv_toaster.ipo_receiver = this

    // Propor duas acoes ; timeout 0 = o toast aguarda a decisao do utilizador
    inv_toaster.of_reset()

    inv_toaster.is_title   = "Atualização disponível"
    inv_toaster.is_text    = "A versão 2.0 está pronta a ser instalada."
    inv_toaster.il_timeout = 0
    inv_toaster.of_add_button(/*chave*/ "installer", /*rotulo*/ "Instalar")
    inv_toaster.of_add_button(/*chave*/ "plus_tard", /*rotulo*/ "Mais tarde")
    inv_toaster.of_show()

    // event ue_toast_notified da window, mapeado em 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

### Reagir ao clique na mensagem #

    // event ue_toast_clicked de inv_toaster : (string as_id)
    // O utilizador clicou no corpo do toast : abrir o ecra correspondente
    Open(w_journal_import)

### Notificar a partir de um processamento demorado #

    // Fim de uma importacao : informar sem bloquear o ecra de introducao de dados
    inv_toaster.of_reset()

    if ll_erreurs = 0 then
        inv_toaster.is_kind = inv_toaster.KIND_SUCCESS
        inv_toaster.is_text = "Importação concluída: [b]" + String(ll_lignes) + " linhas[/b] integradas."
    else
        inv_toaster.is_kind    = inv_toaster.KIND_ERROR
        inv_toaster.il_timeout = 0                    // um erro tem de ser lido
        inv_toaster.is_text    = "Importação interrompida: " + String(ll_erreurs) + " erros."
    end if

    inv_toaster.of_show()

## Boas práticas #

- Uma instância persistente por window (variável de instância criada na abertura) em vez de uma criação/destruição a cada mensagem: a ligação `ipo_receiver` mantém-se e os retornos chegam.
- Convém chamar `of_reset()` antes de cada notificação: sem isso, o título, a imagem ou os botões da anterior permanecem definidos.
- `il_timeout = 0` deve reservar-se às mensagens que exigem uma decisão (erro bloqueante, ação proposta): um toast que não desaparece sozinho acaba por incomodar.
- `ib_screen = true` deve utilizar-se apenas para o que tem de permanecer visível quando a aplicação está em segundo plano (fim de processamento demorado, tarefa noturna).
- Um toast é uma mensagem transitória: se for absolutamente necessária uma resposta antes de continuar, deve utilizar-se a messagebox, que bloqueia e devolve a escolha.
- Para um estado permanente em vez de uma notificação, é preferível a statusbar.

← Referência dos componentes · Índice do guia