-
Notifications
You must be signed in to change notification settings - Fork 1
Events
Go-gui's event system routes input events through the layout tree to the correct widget. The routing rules are simple and deterministic — no hidden event bus, no observer subscriptions, no lifecycle hooks.
Every event callback receives a *gui.Event. The fields relevant to a given callback
depend on the event type; unused fields are zero.
type Event struct {
// Mouse
MouseX float32 // cursor X relative to the receiving shape
MouseY float32 // cursor Y relative to the receiving shape
MouseDX float32 // horizontal movement delta
MouseDY float32 // vertical movement delta
MouseButton MouseButton // MouseLeft, MouseRight, MouseMiddle
// Scroll
ScrollX float32
ScrollY float32
// Keyboard
KeyCode KeyCode
Modifiers Modifier // Shift, Ctrl, Alt, Super
CharCode uint32 // Unicode code point (EventChar only)
// Touch
NumTouches int
Touches [8]TouchPoint
// Gesture
GestureType GestureType
GesturePhase GesturePhase // Began, Changed, Ended, Cancelled
// Misc
Type EventType
FilePath string // EventFileDropped
FrameCount uint64
// Propagation
IsHandled bool
}| Constant | When it fires |
|---|---|
EventKeyDown |
Key pressed (repeats while held) |
EventKeyUp |
Key released |
EventChar |
Printable character typed (after OS key mapping) |
EventMouseDown |
Mouse button pressed |
EventMouseUp |
Mouse button released |
EventMouseMove |
Cursor moved |
EventMouseScroll |
Mouse wheel or trackpad scroll |
EventMouseEnter |
Cursor entered a shape's bounds |
EventMouseLeave |
Cursor left a shape's bounds |
EventTouchesBegan |
Touch contact started |
EventTouchesMoved |
Touch contact moved |
EventTouchesEnded |
Touch contact lifted |
EventGesture |
Recognised gesture (tap, pan, pinch, etc.) |
EventResized |
Window resized |
EventFocused |
Window gained focus |
EventUnfocused |
Window lost focus |
EventFileDropped |
File dragged onto the window |
EventClipboardPasted |
Clipboard paste |
The routing logic is built into the framework and runs automatically each frame.
Mouse events route to the topmost (last-drawn) shape whose bounds contain the cursor. The tree is traversed in reverse child order so later children occlude earlier ones:
Mouse click at (x, y)
→ walk children in reverse
→ first child containing (x, y)
→ deliver OnClick / OnMouseDown
→ if e.IsHandled = true, stop
Keyboard events (EventKeyDown, EventChar) route to the currently focused element.
Focus is tracked by IDFocus — see Focus and Scrolling.
Scroll events route to the innermost IDScroll container whose bounds contain the
cursor. If that container has no scroll handler of its own, the event propagates up
to the next IDScroll ancestor.
Set e.IsHandled = true in any callback to prevent the event from being delivered to
further handlers. This is the idiomatic go-gui pattern:
OnClick: func(_ *gui.Layout, e *gui.Event, w *gui.Window) {
doSomething(w)
e.IsHandled = true // stop propagation
},If you don't set IsHandled, the event continues up the tree. This is intentional for
cases like a scrollable list inside a scrollable panel — the inner list handles vertical
scroll, but horizontal scroll can bubble to the outer container.
Event callbacks are attached to shapes via fields on the widget config struct. The full
set available on EventHandlers:
| Callback | Trigger |
|---|---|
OnClick |
Mouse button released inside shape |
OnMouseDown |
Mouse button pressed inside shape |
OnMouseUp |
Mouse button released (anywhere, if locked) |
OnMouseMove |
Cursor moved |
OnMouseEnter |
Cursor entered shape |
OnMouseLeave |
Cursor left shape |
OnMouseScroll |
Scroll wheel over shape |
OnKeyDown |
Key pressed while shape is focused |
OnKeyUp |
Key released while shape is focused |
OnChar |
Printable character while shape is focused |
OnFocus |
Shape gained keyboard focus |
OnBlur |
Shape lost keyboard focus |
All callbacks share the same signature:
func(layout *gui.Layout, e *gui.Event, w *gui.Window)The layout argument is the node that received the event; e carries the event data;
w is the window — the same w available in the view function.
e.Modifiers is a bitmask. Test individual modifiers:
OnKeyDown: func(_ *gui.Layout, e *gui.Event, w *gui.Window) {
if e.KeyCode == gui.KeyS && e.Modifiers.Has(gui.ModCtrl) {
save(w)
e.IsHandled = true
}
},Modifier constants: ModShift, ModCtrl, ModAlt, ModSuper (⌘ on macOS).
For events not tied to a specific shape — file drops, window focus changes, quit requests — attach a handler to the window directly:
w.OnEvent = func(e *gui.Event, w *gui.Window) {
switch e.Type {
case gui.EventFileDropped:
loadFile(w, e.FilePath)
case gui.EventQuitRequested:
if !app.Saved {
w.Dialog(gui.DialogCfg{
Title: "Unsaved changes",
Body: "Save before quitting?",
// ...
})
}
}
}Mouse coordinates in callbacks are relative to the receiving shape's top-left corner
after the framework applies any rotation (via RotatedBox). If you need window-absolute
coordinates, read layout.Shape.X and layout.Shape.Y from the *gui.Layout argument
and add the relative offset.
Getting Started
Widgets
Layout & Interaction
Visuals
Reference