Avancé
August 16, 2026 · View on GitHub
Raccourcis clavier, goroutines asynchrones, notifications toast, modaux, onglets, accordéon, menus contextuels et tout ce qui ne rentre pas parfaitement dans les pages précédentes.
## Notifications Toast
Un message chronométré qui apparaît, reste quelques secondes et disparaît sur son propre. Pas de dialogue, pas de blocage de l'utilisateur.
type UI struct {
toast proton.ToastState
}
// trigger from anywhere — goroutine-safe
u.toast.Show("File saved!", 2*time.Second)
// draw it LAST in your draw function so it renders on top of everything
proton.Toast(ctx, &u.toast)
S'il n'y a pas de toast actif, « Toast » ne dessine rien. Pas besoin de vérifier au préalable.
func (t *ToastState) Show(msg string, duration time.Duration)
proton.Toast(ctx proton.Context, state *proton.ToastState)
Superposition / Modal
Une toile de fond grisée avec un contenu centré au-dessus de tout.
type UI struct {
modal proton.OverlayState
openBtn proton.Clickable
closeBtn proton.Clickable
}
// open it
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.Button(ctx, &u.openBtn, "Open dialog") {
u.modal.Show()
}
})
// draw it — also at the end of your draw function
proton.Overlay(ctx, &u.modal, func(ctx proton.Context) {
proton.MinSize(ctx, 300, 0, func(ctx proton.Context) {
proton.Card(ctx, proton.RGB(0x2e3440), 12, 24, func(ctx proton.Context) {
proton.H5(ctx, "Are you sure?")
proton.Gap(ctx, 8)
proton.Label(ctx, "This cannot be undone.")
proton.Gap(ctx, 20)
proton.RowEnd(ctx,
func(ctx proton.Context) {
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.OutlineButton(ctx, &u.closeBtn, "Cancel") {
u.modal.Hide()
}
})
},
func(ctx proton.Context) { proton.Gap(ctx, 8) },
func(ctx proton.Context) {
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.Button(ctx, &u.openBtn, "Confirm") {
u.modal.Hide()
doThing()
}
})
},
)
})
})
})
func (o *OverlayState) Show()
func (o *OverlayState) Hide()
func (o *OverlayState) Toggle()
proton.Overlay(ctx proton.Context, state *proton.OverlayState, content func(proton.Context))
Overlay ne dessine rien lorsque state.Visible est faux, vous pouvez donc l'appeler
chaque image sans aucune condition d'emballage.
Raccourcis clavier
Enregistrez une fonction à déclencher lorsqu'une combinaison de touches est enfoncée.
proton.OnKey(ctx, proton.ModCtrl, "S", func() { save() })
proton.OnKey(ctx, proton.ModCtrl, "Z", func() { undo() })
proton.OnKey(ctx, proton.ModCtrl|proton.ModShift, "N", func() { newFile() })
proton.OnKey(ctx, proton.ModNone, proton.KeyEscape, func() { closeDialog() })
Appelez « OnKey » dans votre fonction de dessin. Il enregistre le raccourci pour cela cadre. Puisque la fonction de dessin exécute chaque image, les raccourcis restent actifs tant que la fenêtre est ouverte.
proton.OnKey(ctx proton.Context, modifiers proton.Modifier, keyName string, fn func())
Constantes du modificateur :
proton.ModNone // no modifier — just the key
proton.ModCtrl // Ctrl (Cmd on macOS)
proton.ModShift
proton.ModAlt
// combine with |
proton.ModCtrl | proton.ModShift
Constantes du nom de clé (pour les clés autres que des lettres) :
proton.KeyEscape
proton.KeyReturn
proton.KeyBackspace
proton.KeyDelete
proton.KeyTab
proton.KeySpace
proton.KeyUp
proton.KeyDown
proton.KeyLeft
proton.KeyRight
Les clés alphabétiques ne sont que des chaînes : "S", "Z", "N", "A".
Onglets
Une barre d'onglets horizontale avec une zone de contenu qui change en fonction du onglet sélectionné.
type UI struct {
tabs proton.TabState
tabBtns [3]proton.Clickable
}
proton.Tabs(ctx,
[]string{"Files", "Settings", "About"},
u.tabBtns[:],
&u.tabs,
func(ctx proton.Context, i int) {
switch i {
case 0: drawFiles(ctx)
case 1: drawSettings(ctx)
case 2: drawAbout(ctx)
}
},
)
u.tabs.Selected contient l'index de l'onglet actif. Vous pouvez le définir par programme
pour changer d'onglet à partir du code.
proton.Tabs(ctx proton.Context, labels []string, btns []proton.Clickable, state *proton.TabState, content func(proton.Context, int))
La tranche btns a besoin d'un Clickable par onglet. Passer u.tabBtns[:] est
la manière idiomatique lorsque vous déclarez un tableau de taille fixe dans votre structure.
Accordéon
Une section pliable avec un en-tête cliquable.
type UI struct {
sec1 proton.AccordionState
sec1btn proton.Clickable
}
proton.Accordion(ctx, &u.sec1, &u.sec1btn, "Advanced Options", func(ctx proton.Context) {
proton.Label(ctx, "These options are hidden until the user expands this.")
proton.Gap(ctx, 8)
proton.Toggle(ctx, &u.expertMode, "Expert mode")
})
proton.Accordion(ctx proton.Context, state *proton.AccordionState, btn *proton.Clickable, title string, content func(proton.Context))
state.Open suit s'il est développé. Vous pouvez le configurer directement pour démarrer
un accordéon ouvert : u.sec1.Open = true.
Menu contextuel
Un menu contextuel qui apparaît à la position du curseur.
type UI struct {
menu proton.ContextMenuState
menuTag proton.FrameTag
}
items := []proton.ContextMenuItem{
{Label: "Copy"},
{Label: "Paste"},
{Label: "Delete"},
{Label: "Disabled option", Disabled: true},
}
chosen := proton.ContextMenu(ctx, &u.menu, &u.menuTag, items, func(ctx proton.Context) {
proton.Label(ctx, "Right-click anywhere in this area")
})
if chosen >= 0 {
fmt.Println("User picked:", items[chosen].Label)
}
proton.ContextMenu(ctx proton.Context, state *proton.ContextMenuState, tag *proton.FrameTag, items []proton.ContextMenuItem, content func(proton.Context)) int
Renvoie -1 lorsque rien n'a été sélectionné et l'index de l'élément sur le cadre quelque chose clique. Le menu se ferme automatiquement après une sélection.
Mises à jour asynchrones et Goroutines
Votre fonction draw s’exécute sur le thread principal. Quand une goroutine termine son travail
et change d'état, appelez ctx.Invalidate() pour demander un redessin.
type UI struct {
loading bool
result string
fetchBtn proton.Clickable
}
// in your draw function
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.Button(ctx, &u.fetchBtn, "Fetch") && !u.loading {
u.loading = true
go func() {
data := fetchFromAPI() // takes a while
u.result = data
u.loading = false
ctx.Invalidate() // wake up the render loop
}()
}
})
if u.loading {
proton.Row(ctx,
func(ctx proton.Context) { proton.Spinner(ctx, &u.spin, 18) },
func(ctx proton.Context) { proton.Gap(ctx, 8) },
func(ctx proton.Context) { proton.Muted(ctx, "Loading...") },
)
} else if u.result != "" {
proton.Label(ctx, u.result)
}
Sans ctx.Invalidate(), la fenêtre ne sera pas redessinée jusqu'à ce que l'utilisateur bouge
la souris ou interagit avec elle. Appelez-le toujours après avoir changé d'état de
une goroutine.
Tourneur
Un indicateur de chargement animé. L'appel de Spinner conserve automatiquement le
redessinage de la fenêtre — aucune boucle Invalidate() nécessaire.
type UI struct {
spin proton.SpinnerState
}
proton.Spinner(ctx, &u.spin, 32) // 32dp diameter
proton.Spinner(ctx proton.Context, state *proton.SpinnerState, sizeDp float32)
SpinnerState suit l'heure de début de l'animation. Déclarez-en un par spinner
dans votre structure d'état.
SelectBox (liste déroulante)
type UI struct {
langSel proton.SelectBoxState
}
langs := []string{"Go", "Rust", "Zig", "C", "Python"}
i := proton.SelectBox(ctx, &u.langSel, langs)
proton.Caption(ctx, "Selected: "+langs[i])
La liste déroulante s'ouvre sous le bouton et se ferme lors d'une sélection ou d'un clic extérieur.
proton.SelectBox(ctx proton.Context, state *proton.SelectBoxState, options []string) int
If — Rendu conditionnel
Rend le contenu uniquement lorsqu'une condition est vraie. Enregistre un bloc if lorsque vous
je veux juste afficher ou masquer un seul widget.
proton.If(ctx, user.IsAdmin, func(ctx proton.Context) {
proton.Pad(ctx, 4, func(ctx proton.Context) {
if proton.Button(ctx, &u.deleteBtn, "Delete everything") {
deleteEverything()
}
})
})
proton.If(ctx proton.Context, cond bool, content func(proton.Context))
FocusArea — Gestion des clés étendues
Lorsque vous avez besoin d'événements de clavier actifs uniquement dans une région spécifique de l'interface utilisateur, pas globalement. Habituellement, « OnKey » suffit – utilisez-le lorsque vous en avez deux panneaux qui doivent avoir des raccourcis clavier indépendants.
type UI struct {
editorTag proton.FrameTag
}
proton.FocusArea(ctx, &u.editorTag, "A", func(ctx proton.Context) {
proton.TextArea(ctx, &u.text, "Type here...")
})
proton.FocusArea(ctx proton.Context, tag *proton.FrameTag, keyName string, content func(proton.Context))
Options de la fenêtre
// fullscreen
a.WindowEx("App", 800, 600, []proton.WindowOption{
proton.Fullscreen(),
}, draw)
// maximized
a.WindowEx("App", 800, 600, []proton.WindowOption{
proton.Maximized(),
}, draw)
proton.Fullscreen() proton.WindowOption
proton.Maximized() proton.WindowOption
Maintenir les animations en cours d'exécution
Proton ne redessine que lorsqu'il y a une entrée de l'utilisateur ou que vous appelez ctx.Invalidate().
Pour les animations : barres de progression qui se remplissent au fil du temps, comptes à rebours, n'importe quoi
basé sur le temps — appelez « Invalidate » à la fin de chaque image pour conserver les redessins
va:
func draw(ctx proton.Context, u *UI) {
if u.animating {
u.progress += 0.01
if u.progress >= 1.0 {
u.progress = 0
u.animating = false
}
proton.ProgressBar(ctx, u.progress)
ctx.Invalidate() // draw again next frame
}
}
Lorsque u.animating devient faux, Invalidate cesse d'être appelé et Proton
revient au redessin uniquement sur la saisie de l'utilisateur. Le widget Spinner fait cela
automatiquement — vous n'avez pas besoin de le gérer vous-même.