Guides
Input
The pointer, the keyboard focus, shortcuts, the keys held, input methods, dropped files, and widgets that take every key.
Elements report what the user did to them since the last frame, as you build them: ask, and handle it where the element is built.
A ui.Element value is valid for one build pass. Its zero value is absent,
and queries on that value return false or zero. Use Valid to check optional
controls. Stale use is diagnosed in development and Tester; production
methods return empty results or ignore the operation.
Store a ui.Handle for focus or shortcuts across builds. Each window has
its own binding for the handle; use that window's Context when querying it.
card := ui.Column(c).Padding(12).Radius(8).Focusable()
if card.Hovered() {
card.Background(t.SurfaceHover)
}
if card.DoubleClicked() {
app.open(item)
}The pointer
Hovered, Pressed, Clicked, DoubleClicked, RightClicked, Dragged
(how far the pointer moved since the last frame while pressing the
element) and PointerPosition. ClickModifiers returns the modifier keys
held for the last click, as Shift for a Shift-click. c.Modifiers() returns the modifier
keys held now, as one goes down or up on its own too, and the view draws
again as they change: a list showing each row's Cmd+1–9 while Cmd is held
reads it. PassThrough lets the
pointer through the element to what is below; HitTest lets it through
where a function of the pointer's position says, as a ring's hole or the
gaps between a pie's segments (see
Drawing); IgnoreInput lets the
pointer through it and those inside it, and keeps the keyboard focus away
from them; with
AccessHidden, which
hides them from assistive technology, they are inert.
Elements that take the pointer give it to the innermost under it: a button in a clickable row takes its own clicks.
PressedInside reports a press on an element or inside it without the
element taking the pointer, where Pressed and Clicked make it take
it: a table's cell notices an Alt-click while its row still chooses:
cell := ui.Box(c).Children(func() { ui.Text(c, value) })
if cell.PressedInside() && c.Modifiers()&ui.Alt != 0 {
app.inspect(row, col)
}Pressed stays true while the pointer is held inside the element. A
release that comes with moves no build has read leaves the element
pressed for the build that reads them, with the pointer where it let go
(PointerPosition), and released in the build after. Observing
the start of a press rebuilds the view before painting, so a row chosen by
if row.Pressed() || row.Clicked() shows its new selection and focus colors
in that frame. Guard the choice against selecting the same item again.
Clicked reports activation on release inside the element, or through the
keyboard or assistive technology; buttons use that activation.
Pressed, Clicked, Dragged, DragVelocity and DragEnded follow the
primary button alone, as CSS's :active and the web's drag gestures do
(event.button === 0): a right or middle drag moves no card, slider or
split, and only the right-click (RightClicked) or context
menu it opens comes of it. PressedButton returns the
button pressing the element, whichever it is (0 the primary, 1 the
secondary, 2 the middle), while the press lasts; with PointerPosition,
it builds a gesture of another button, as a canvas panning with the
middle button:
x, y, _ := canvas.PointerPosition()
if b, ok := canvas.PressedButton(); ok && b == 2 {
if app.panning {
app.pan.X += x - app.last.X
app.pan.Y += y - app.last.Y
}
app.panning, app.last = true, ui.Point{X: x, Y: y}
} else {
app.panning = false
}Every press comes from the primary pointer: the mouse, or the first finger
on a touchscreen; a second finger presses nothing, in browsers too
(isPrimary). HandleInput hears every
button go down and up, with InputEvent.Button.
PressedPointer tells what presses the element, and how hard, as
Pointer Events' pointerType and pressure: PointerMouse (a mouse or
a touchpad), PointerPen (a pen on a tablet or a screen) or
PointerTouch (a finger on a touchscreen), and a pressure from 0 to 1
as of the press's last move. A device that tells no pressure, as a
mouse, presses 0.5 while a button is down. HandleInput gets the same
on every InputPointerDown, InputPointerMove and InputPointerUp
(InputEvent.Pointer and Pressure), every move as it comes rather
than once a frame, and a pen pressing harder or softer where it stands
comes as a move too. A signature pad widens its stroke with a pen's
pressure, and goes by the pointer's speed for a mouse:
ui.Box(c).Grow(1).Draw(app.paint).HandleInput(func(ev ui.InputEvent) bool {
switch {
case ev.Kind == ui.InputPointerDown, ev.Kind == ui.InputPointerMove && ev.Button == 0:
width := app.widthBySpeed(ev.X, ev.Y, ev.Time) // a mouse's, or a finger's
if ev.Pointer == ui.PointerPen {
width = 1 + 5*ev.Pressure
}
app.stroke = append(app.stroke, stroke{ev.X, ev.Y, width})
return true
}
return ev.Kind == ui.InputPointerUp
})The systems tell the device of each press and move: AppKit's tablet
events on macOS (a mouse event's subtype and pressure, and
tabletPoint: between moves), the WM_POINTER messages of pens and
fingers on Windows, which Windows turns into the mouse messages it
marks as theirs, GDK's source device and pressure axis on Linux, and
PointerEvent in browsers. A Tester presses as a pen or a finger
after UsePointer(ui.PointerPen, 0.6).
A press the system cancels
The system can end a press without the pointer letting go, as Pointer
Events' pointercancel: a browser turning a touch into the page's
scrolling, a menu or another window taking the mouse while a button is
down, a pen or a finger Windows gives up on. The window losing the
keyboard ends a press the same way. Such a press commits nothing a release
would: it clicks nothing (Clicked, RightClicked), drops no value it
drags (Drop), ends no drag (DragEnded), opens no context menu, and a
Draggable element goes back to where the press took it. PressCanceled
reports it, in the frame after, for a gesture to undo what it began:
dx, _, held := handle.Dragged()
switch {
case held:
app.preview += dx
case handle.PressCanceled():
app.preview = 0 // as it was
case app.preview != 0:
app.width, app.preview = app.width+app.preview, 0
}HandleInput still gets the press's InputPointerUp, so that what it
holds lets go, with InputEvent.Canceled set: roll back rather than
commit.
ui.Box(c).Size(120, 40).HandleInput(func(ev ui.InputEvent) bool {
switch ev.Kind {
case ui.InputPointerDown:
app.armed = true
case ui.InputPointerUp:
if app.armed && !ev.Canceled {
app.fire()
}
app.armed = false
}
return true
})A Tester cancels a press with CancelPress.
While the pointer presses an element, the elements it was over as the
press began, as the row around a button, stay Hovered as long as it is
over them, as in CSS, and the others hover no more: dragging over other
elements does not light them up. A button that shows while its row is
hovered stays as it is pressed, to take its click:
row := ui.Row(c).Padding(8)
hovered := row.Hovered()
row.Children(func() {
ui.Text(c, item.Title).Grow(1)
if hovered && ui.Button(c, "Remove").Clicked() {
removed = item.ID
}
})Built only while the row is hovered, the button is not there for
assistive technology. IgnoreInput keeps it built, transparent and out of
the pointer's and the keyboard's way at rest, still read and pressed by
assistive technology (see Accessibility):
del := ui.Button(c, "Remove").IgnoreInput(!hovered)
if !hovered {
del.Opacity(0)
}The keyboard focus
Focusable elements take the focus when clicked, and Tab and Shift+Tab
move it between them, with a focus ring when it moves by keyboard.
AutoFocus gives an element the focus when it appears, such as the first
field of a dialog, and Focus keeps it there while you call it; Focused,
FocusVisible and FocusWithin report it. Enter and Space press a focused
button, and Space toggles a focused check box, switch, toggle or radio
button. A focused link, or an element with Role(ui.RoleLink), takes Enter
alone, as in a web page: Space scrolls the container around it.
When the focus moves to an element by the keyboard or by the app (Focus,
AutoFocus, a handle's Focus, or a field bound with
FocusBind), the scroll containers around it scroll
as little as shows it, as the web's element.focus() does, so keyboard
navigation of your own keeps the focus in sight. It scrolls once, as the
focus moves: calling Focus in every frame leaves the user's scrolling
alone. A click focuses an element where it is, and so does Focus while
a pointer button is held, as for a line that takes the focus from a press
on its grip. To line it up otherwise, call ScrollIntoView with its
options in the frame it takes the focus. To keep the scroll where it is,
as the web's focus({ preventScroll: true }), pass ui.FocusOptions:
cell.Focus(ui.FocusOptions{PreventScroll: true}) gives the focus back to
a cell of a grid when a dialog closes, without scrolling the grid. It holds
through a field that gives the focus to its input. The zero options are
those of Focus() without them.
TabStop(false) makes an element focusable but out of Tab's order, as
HTML's tabindex="-1": it takes the focus from a click, from Focus and
AutoFocus, and from assistive technology, while Tab and Shift+Tab pass
over it, on to the controls inside it and after it. A card or a panel
takes the focus so to hold its keys without coming before its own
buttons; TabStop(true) puts it back in the order. In a focus
group, Tab enters at a member it stops at, while the
arrows still reach the others.
card := ui.Column(c).Padding(12).TabStop(false).Children(func() {
ui.Text(c, item.Title)
ui.Button(c, "Open") // Tab goes here, past the card
})
if card.Shortcut(0, ui.KeyDelete) {
app.remove(item)
}FocusLeft reports that the focus left an element since the last frame:
it was on the element or inside it, and is now outside it, or nowhere, as
the window lost the keyboard. That is the web's focusout whose
relatedTarget the element does not contain. The overlays attached to the
element and to those inside it (AttachTo), as a combobox's list or a
button's popover, are inside it, as for PressedOutside, so a popup field
closes as the focus leaves it but not as it moves into its own list:
var field ui.Element
combo := ui.Column(c).Children(func() {
field = ui.TextInput(c, &app.query)
})
if combo.FocusLeft() {
app.open = false
}
if app.open {
ui.Overlay(c, func() {
ui.Column(c).AttachTo(field, ui.AnchorBottomLeft, ui.AnchorTopLeft).Children(app.options)
})
}Where the focus went is what the elements report as they are built
(Focused, FocusWithin), and a handle's
FocusWithin(c) before its control is built.
c.FocusTakesText() reports whether the focus takes text, typed or
composed: a text input or text area, or an editor of rich text or code,
that is not read only, or an element that takes text itself
(HandleTextInput, TextCaret). It is the web's guard of single-key
shortcuts (isContentEditable, an input or a textarea): a command
palette's letters step aside while a field types them.
if !c.FocusTakesText() && c.Shortcut(0, ui.KeyK) {
app.palette = true
}Persistent focus
Bind an app-owned handle to a control on each build:
ui.TextInput(c.Key("search"), &app.query).Bind(&app.search)Declare search ui.Handle in your app state. app.search.Focus() requests
focus and waits while the control is hidden. CancelFocus() cancels the
request. Focused(c) and FocusWithin(c) read the control's identity before
or after it is built. c.Resolve(app.search) returns only this pass's element.
app.search.Bounds(c) reads its box from the committed frame, including
between builds in an input callback. Closing a window cancels its focus
request.
A handle can bind in several windows. Queries take that window's Context;
use Focus(c) or CancelFocus(c) to select a window explicitly. The
no-argument focus form requires at most one open binding.
Focus bound to data
type pane int
const (none pane = iota; files; diff)
ui.Column(c.Key("files")).FocusBind(&app.pane, files).Children(func() { app.filesView(c) })
ui.Column(c.Key("diff")).FocusBind(&app.pane, diff).Children(func() { app.diffView(c) })
// In an action:
app.pane = diff
// Actual focus in this window:
focused := ui.FocusedValue(c, &app.pane)FocusBind takes a pointer to a comparable field and a matching value.
Each value names one control in the window; reserve zero for no focus.
The field holds desired focus. A request waits if its control is hidden,
so use FocusedValue to read actual focus while it waits. Assigning zero
clears focus. User focus changes update the field when no request is pending.
Use separate fields for separate windows.
Focus groups
The controls of a toolbar, a radio group, a segmented control or a tab list
are one stop of Tab, as they are natively: Tab moves the focus into the
group, to the control that had it last (or the radio button or tab chosen),
and on out of it, while the arrows move it among them, Home and End to the
first and last. FocusGroup does it for a container of your own, with the
arrows ui.Horizontal, ui.Vertical or both; a slider or a text input
inside keeps the arrows it takes.
ui.Row(c).Gap(4).FocusGroup(ui.Horizontal).Children(func() {
for _, tool := range tools {
if ui.Button(c, tool.Name).Clicked() {
app.tool = tool
}
}
})Shortcuts
c.Shortcut(ui.Cmd, ui.KeyS) reports a key pressed with exactly those
modifiers anywhere in the window, and Element.Shortcut only while the
element or one inside it has the focus, which comes first. A focused button
keeps Enter and Space, a link Enter, and a toggle Space, so
c.Shortcut(0, ui.KeyEnter) presses a dialog's default button wherever
else the focus is.
if c.Shortcut(ui.Cmd, ui.KeyS) {
app.save()
}A button doing what a shortcut does tells screen readers its key with
KeyShortcuts, as
ui.Button(c, "Save").KeyShortcuts(ui.Cmd, ui.KeyS).
A key held down repeats, and each repeat comes as a press again, so an
action that steps runs while the key is held. c.ShortcutRepeated() tells
whether the key that the last shortcut to report one took came as such a
repeat, as the web's KeyboardEvent.repeat, whichever shortcut that was
(the window's, an element's, a list's or an overlay's), and an
OnShortcut action can ask it as it runs. A toggle takes the press alone:
if c.Shortcut(0, ui.KeyUp) {
app.count++ // and again while Up is held
}
if c.Shortcut(0, ui.KeyT) && !c.ShortcutRepeated() {
app.panel = !app.panel // once a press
}A repeat the action leaves is still taken: it goes to no other shortcut. GTK flags no repeat, so on Linux a press of a key held already, with no release between, is one, as X11 (with detectable auto-repeat, which GDK turns on) and Wayland send them.
ui.Cmd is Command on macOS and Ctrl elsewhere. ui.KeyBack and
ui.KeyForward are the back and forward buttons of a mouse and the keys of
keyboards that have them, which a router takes. Shortcuts
of menus still work, and the Edit menu's roles (cut, copy,
paste, select all, undo, redo) act on the focused text input. Where the
focus takes no such command, as a list with nothing editable, the role
goes to the shortcuts of its key, so c.Shortcut(ui.Cmd, ui.KeyV) sees
Paste on macOS too, where the menu takes Command+V before the window. The
Edit menu enables a role's item only while the focus, or such a shortcut,
would take it.
Shortcuts work on layouts that type no Latin letters too: with Cmd, Ctrl,
Alt or Super held, a key typing a letter of no US key, as ы on a Russian
layout, stands for the key at its place on a US keyboard, so Cmd+ы is
ui.KeyS. Typed without those modifiers, it stays text.
A focused text input takes the editing keys of the platform first: on macOS, Option and Command with the arrows and Backspace, and Control with A, E, B, F, N, P, D, H and K, as in other Mac apps; elsewhere, it leaves Alt and the arrows, which go back and forward, and the function keys.
A shortcut action can be declared before binding its control:
app.filesView.OnShortcut(c, ui.Cmd, ui.KeyK, app.openSelected)
ui.List(c.Key("files"), &app.list, len(app.files)).Bind(&app.filesView).
Rows(func(row ui.ListRow) {
ui.Text(row.Context, app.files[row.Index].Name)
})Handle.OnShortcut runs after construction only when the window built an
enabled control for that handle. Hidden controls take no command.
c.OnShortcut declares an action in the current parent scope; the root
Context handles keys left by the focused control and active overlays.
Use Element.OnShortcut for an action inside an element's focus subtree.
c.OnWindowShortcut declares the window's action wherever c is, as
c.Shortcut reports it: a component built deep in the view, as a hint
that shows its key and runs it, hears it while the focus is anywhere else,
or nowhere. Built inside a dialog, it runs while the dialog shows; built
behind one, it waits.
func shortcutHint(c *ui.Context, key ui.Key, label string, run func()) {
c.OnWindowShortcut(ui.Cmd, key, run)
ui.Text(c, label) // "⌘K"
}Keys held
c.KeyDown(ui.KeyK) reports whether a key is held down in the window, and
c.KeysDown() lists the keys held, in the order they went down, whichever
element has the focus and whether or not it took them: they only watch,
so the focused field still types. A view that reads them draws again as
they change. A cheat sheet of shortcuts lights the key caps held while its
search field has the focus, and fades the rows that use none of them:
ui.SearchField(c, &app.query).Label("Search shortcuts").AutoFocus()
held, mods := c.KeysDown(), c.Modifiers()
for _, s := range app.shortcuts {
row := shortcutRow(c, s, held, mods) // lights the caps held
if (len(held) > 0 || mods != 0) && !s.uses(held, mods) {
row.Opacity(0.4)
}
}Keys count as shortcuts take them, so Cmd+ы on a Russian layout holds
ui.KeyS; the modifier keys are c.Modifiers()'s. A key whose press runs
a menu item's shortcut goes to the menu, not the window. The window lets
go of every key as it loses the keyboard to another window or app, where
their releases go, and of each key whose release the system keeps from
it: on macOS, and in a Mac's browsers, one let go of while Command is
held; on Windows, one held as a menu opens, as with Alt and a letter or
Alt and Space, whose release the menu takes; and one whose release an
input method takes, as it begins composing while the key is held. A
Tester holds keys with KeyDown and KeyUp, and SetFocused(false)
lets go of them.
Input methods
Text inputs take text composed with input methods, which see the text around the caret: macOS's press and hold replaces the letter it accents, Japanese input methods convert typed text again, and others predict from what comes before.
Dropped files
DroppedFiles returns the paths of the files dropped on the element from
Finder, Explorer or a file manager, and FileDragOver reports files dragged
over it, to show it would take them. The window takes files only over such
elements, or anywhere when it has OnFileDrop listeners, which get the
files no element takes, with where they were dropped:
zone := ui.Column(c).Size(260, 80).Border(1, t.Border)
if files := zone.DroppedFiles(); files != nil {
app.files = files
}
if zone.FileDragOver() {
zone.Border(2, t.Accent)
}Values dragged within or between windows, and serialized data exchanged with other applications, are drag and drop.
Scrolling and pinching
Scroll containers scroll with the wheel and the touchpad on their own. An
element that does something else with them, as a chart zooming and
panning its candles, gets them with HandleInput: ui.InputScroll
scrolls by DX and DY DIPs, positive down and to the right of the
content, with X and Y the pointer relative to the element; Precise
tells a touchpad, which scrolls by pixels, from a mouse wheel's notches.
ui.InputZoom is a touchpad's pinch, at X and Y, zooming by Scale:
2 shows the content twice as large as it was. macOS, Linux on Wayland and
browsers report pinches; Windows sends them as scrolling with Ctrl held,
so there a touchpad's scrolling with Ctrl comes as ui.InputZoom too,
while a mouse wheel's notches stay ui.InputScroll with ui.Ctrl in
Mods. On Linux a touchscreen scrolls and pinches as a touchpad does: a
finger dragging scrolls the content under it along with it (Precise),
two fingers pinch, between them, and scroll as they move together, a tap
clicks and a finger held still for half a second right-clicks. What the
element takes no scroll container around it scrolls with; what it
leaves, by returning false, they do.
ui.Box(c).Fill().HandleInput(func(ev ui.InputEvent) bool {
switch {
case ev.Kind == ui.InputZoom:
app.chart.zoom(ev.X, ev.Scale)
case ev.Kind == ui.InputScroll && !ev.Precise:
// A notch, which scrolls further on some platforms than on
// others, zooms by a quarter.
if ev.DY < 0 {
app.chart.zoom(ev.X, 1.25)
} else if ev.DY > 0 {
app.chart.zoom(ev.X, 0.8)
}
case ev.Kind == ui.InputScroll:
app.chart.pan(ev.DX)
default:
return false
}
return true
})A Tester scrolls with Scroll (a touchpad), Wheel (a notch) and
Pinch.
Every key, as it comes
Widgets that take every key themselves, as the
terminal does, get their input as it comes with
HandleInput, before the next frame: keys pressed and released, text typed
and composed while they have the focus, the edit commands of the menus,
and the pointer pressed on them, moving over them or scrolling over them.
InputEvent.PhysicalKey is the key by its place on a US keyboard,
whatever the layout types, for keys that go by position, as a game's WASD.
The function reports whether it took the event; one it leaves goes on to
shortcuts, Tab, context menus and scroll containers as usual, and keys that
the window or an element around the focus handles with Shortcut go there
first. TextCaret turns on the system's input methods for such an element
while it has the focus, composing at the caret it gives; InputMethod(false)
turns them off again while keys still type text, as for Vim's normal mode,
except in browsers, which cannot. c.ReadClipboard,
c.WriteClipboard and c.OpenURL copy, paste and open links for them;
c.OpenURLThen opens a link too, and its function gets what came of it a
moment later, as the system opens it, with an error when no app could,
and the view builds a frame anew. c.WriteClipboardThen copies text the
same way, as navigator.clipboard.writeText resolves or rejects: its
function gets an error when the text did not get there, as when
another app holds the clipboard, or a browser refuses a write the user
did not act for, so that a copy button can show "Copied" or "Failed"
(a Tester refuses them after FailClipboard). Services.ReadClipboardData and
WriteClipboardData read and write several formats at once, as a rich
text editor copies its own, HTML and plain text, and
ClipboardFormats lists the clipboard's without reading them, as a
terminal decides what a paste reads. A text field of the app's
own gets InputSelect when a screen reader moves its selection: the runes
Start to End of the text its Access describes.
ui.Box(c).Fill().Focusable().HandleInput(func(ev ui.InputEvent) bool {
switch ev.Kind {
case ui.InputKeyDown:
return app.key(ev.Mods, ev.Key)
case ui.InputText:
app.insert(ev.Text)
return true
}
return false
}).TextCaret(app.caretRect())
c.OpenURLThen(url, func(err error) {
if err != nil {
c.Toast("No app opens " + url)
}
})
if ui.Button(c, "Copy").Clicked() {
c.WriteClipboardThen(link, func(err error) {
app.copied, app.copyFailed = err == nil, err != nil
})
}Every key of the keyboard
InputEvent.Code is the key by its position on any keyboard, a ui.KeyCode:
it tells the sides of the modifiers (ui.CodeShiftLeft, ui.CodeAltRight,
ui.CodeSuperLeft) and names the keys ui.Key has no name for, as
ui.CodeNumpad5 or ui.CodeIntlBackslash, the 102nd key beside the left
Shift. Its values are USB HID usage IDs of the keyboard page and its names
those of KeyboardEvent.code, with Super for Meta, so one table turns them
into a guest's scancodes.
HandleInput and ObserveInput get every key the system tells the window
of, as a web page's listeners do. That includes modifiers pressed and released
on their own, a lone tap of Super among them, Caps Lock, Num Lock, Scroll Lock,
Print Screen and Pause, and the keys whose press the layout types no Key for,
as a key held with AltGr. Those come as ui.InputKeyDown and ui.InputKeyUp
with Key being ui.KeyUnknown, and only Code names them. Mods includes
the modifier a press brings and leaves out the one a release takes. A handler
that picks its keys with a switch never notices them; one that reacts to any
key should check that Key is not ui.KeyUnknown.
ui.Box(c).Grow(1).Focusable().AutoFocus().Bind(&app.screen).HandleInput(func(ev ui.InputEvent) bool {
switch ev.Kind {
case ui.InputKeyDown:
return app.guest.press(ev.Code)
case ui.InputKeyUp:
return app.guest.release(ev.Code)
}
return false
})
if !app.screen.Focused(c) {
app.guest.releaseAll() // what is let go of elsewhere never comes up here
}What the function takes goes no further, as Alt, which otherwise opens the
menu bar on Windows and Linux, so return false for keys you don't use;
shortcuts keep working either way. Tester.CodeDown and Tester.CodeUp press
and let go of such keys in tests.
What a system tells has limits:
- Keys the desktop takes for itself never come, as Super where a launcher opens with it, or combinations a window manager binds.
- On macOS Caps Lock comes as a press and a release together as its lock changes, not as the key moves. Fn has no code.
- On Windows AltGr comes as
ui.CodeControlLeftthenui.CodeAltRight, as Windows makes up the left Control, andModsholds Ctrl and Alt. Print Screen comes only as a release, as Windows keeps its press. With both Shifts held, only the last one's release comes. The Windows key's release still opens the Start menu. - In browsers the page gets what the browser leaves it:
Codeis the event'scode. WebViews take the keys while they have the focus.
Input inside a region
ObserveInput has a container see the input that comes to the elements
inside it, as an ancestor listening to the keydown events that bubble up
to it, without taking it: the keys pressed and released (with Repeat),
the text typed and composed and the menus' edit commands while the focus
is the container or inside it, and the pointer pressed and released over
it. A demo that stops playing at the first key pressed in it, or a panel
that closes its hint once one of its fields is typed in, use it:
panel := ui.Column(c).Gap(8).ObserveInput(func(ev ui.InputEvent) {
if ev.Kind == ui.InputKeyDown || ev.Kind == ui.InputText {
app.hint = false
}
})
panel.Children(func() {
if app.hint {
ui.Text(c, "Type a name and an email")
}
ui.TextInput(c, &app.name).Label("Name")
ui.TextInput(c, &app.email).Label("Email")
})The function runs before the focused element gets the input, as UI Events'
capture phase, the outermost container first, and a frame follows. The
input then goes on as it would: to the focused element's HandleInput,
the text input, the shortcuts and Tab, so the function sees the keys a
Shortcut takes too. It does not see the keys the system or the menus
take before the window does (an edit command comes instead), nor the input
of popovers, menus and dialogs the container opens, which are not inside
it. A key that moved the focus, as Tab, comes up where the focus went.
See also
- Context menus and menu buttons.
- Tooltips.
- Custom title bars: windows with native UI.