Complete guide to the GoFlow input handling system
The GoFlow input system provides comprehensive event handling for mouse, keyboard, touch, and gesture input across all supported platforms. It follows a layered architecture from low-level platform events to high-level gesture recognition.
┌─────────────────────────────────────────────────────┐
│ Application Layer │
│ (Widgets: GestureDetector, Focus, TextField) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Input System Layer │
│ • InputRouter │
│ • GestureRecognizer │
│ • FocusManager │
│ • KeyBindingManager │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Platform Backend Layer │
│ • macOS: Cocoa events (NSEvent) │
│ • Windows: Win32 messages (TODO) │
│ • Linux: X11/Wayland events (TODO) │
└─────────────────────────────────────────────────────┘
Represents mouse input events.
type MouseEvent struct {
Type EventType // MouseDown, MouseUp, MouseMove
Button MouseButton // Left, Right, Middle
Position *Offset // Cursor position
Modifiers KeyModifiers // Ctrl, Shift, Alt, etc.
Timestamp time.Time
}Represents keyboard input events.
type KeyboardEvent struct {
Type EventType // KeyDown, KeyUp
Key Key // Physical key code
Code string // Key identifier
Character string // Character typed
Modifiers KeyModifiers // Active modifiers
Repeat bool // Is key repeating
}Represents touch/pointer events.
type TouchEvent struct {
Type EventType // TouchStart, TouchMove, TouchEnd
TouchID int // Unique touch identifier
Position *Offset // Touch position
Force float64 // Touch pressure (0-1)
Timestamp time.Time
}Central hub for routing all input events.
// Create router
router := input.NewInputRouter()
// Route events
router.RouteEvent(mouseEvent)
router.RouteEvent(keyboardEvent)
// Access sub-systems
keyState := router.GetKeyboardState()
mouseManager := router.GetMouseRegionManager()
gestureRecognizer := router.GetGestureRecognizer()
keyBindings := router.GetKeyBindingManager()Define interactive regions that respond to mouse events.
// Create a clickable region
bounds := goflow.NewRectFromLTWH(10, 10, 200, 100)
region := input.NewMouseRegion(bounds)
// Configure behavior
region.Cursor = input.CursorPointer
region.OnClick = func(event *input.MouseEvent) {
fmt.Println("Region clicked!")
}
region.OnEnter = func(event *input.MouseEvent) {
fmt.Println("Mouse entered region")
}
region.OnExit = func(event *input.MouseEvent) {
fmt.Println("Mouse left region")
}
// Register with manager
manager := router.GetMouseRegionManager()
manager.AddRegion(region)Track keyboard state and define shortcuts.
// Check key state
keyState := router.GetKeyboardState()
if keyState.IsKeyPressed(input.KeyControl) {
fmt.Println("Ctrl is pressed")
}
// Define key bindings
kb := router.GetKeyBindingManager()
// Ctrl+S to save
kb.AddBinding(input.KeyS, input.ModifierCtrl, func() {
fmt.Println("Save action triggered")
})
// Ctrl+Shift+P for command palette
kb.AddBinding(input.KeyP, input.ModifierCtrl|input.ModifierShift, func() {
fmt.Println("Command palette opened")
})Recognize complex multi-touch gestures.
recognizer := router.GetGestureRecognizer()
// Tap gesture
recognizer.SetOnTap(func(gesture *input.MultiTouchGesture) {
fmt.Printf("Tapped at %v\n", gesture.FocalPoint)
})
// Double tap
recognizer.SetOnDoubleTap(func(gesture *input.MultiTouchGesture) {
fmt.Println("Double tapped!")
})
// Long press
recognizer.SetOnLongPress(func(gesture *input.MultiTouchGesture) {
fmt.Println("Long press detected")
})
// Pan gesture
recognizer.SetOnGestureUpdate(func(gesture *input.MultiTouchGesture) {
if gesture.Type == input.TouchGesturePan {
fmt.Printf("Panning: delta=%v\n", gesture.Translation)
}
})
// Pinch gesture
recognizer.SetOnPinch(func(gesture *input.MultiTouchGesture) {
fmt.Printf("Pinch scale: %.2f\n", gesture.Scale)
})
// Rotate gesture
recognizer.SetOnRotate(func(gesture *input.MultiTouchGesture) {
fmt.Printf("Rotation: %.2f degrees\n", gesture.Rotation)
})The macOS backend uses Cocoa (NSEvent) for input handling.
import "github.com/base-go/GoFlow/backends/macos"
func init() {
// Required for Cocoa UI operations
runtime.LockOSThread()
}
func main() {
macos.InitApp()
window := macos.NewWindow(800, 600, "My App")
// Set event handlers
window.SetMouseFunc(handleMouse)
window.SetKeyFunc(handleKeyboard)
window.SetResizeFunc(handleResize)
window.Show()
macos.Run() // Blocks - runs Cocoa event loop
}window.SetMouseFunc(func(button, action int, x, y float64) {
switch action {
case 0: // Mouse up
fmt.Printf("Mouse up at (%.0f, %.0f)\n", x, y)
case 1: // Mouse down
fmt.Printf("Mouse down at (%.0f, %.0f)\n", x, y)
case 2: // Mouse move
fmt.Printf("Mouse moved to (%.0f, %.0f)\n", x, y)
}
})window.SetKeyFunc(func(key, action int) {
if action == 1 { // Key down
fmt.Printf("Key pressed: %d\n", key)
} else { // Key up
fmt.Printf("Key released: %d\n", key)
}
})Common key codes:
0: A1: S2: D36: Return/Enter48: Tab49: Space51: Delete53: Escape55: Cmd56: Shift58: Alt59: Ctrl123-126: Arrow keys (Left, Right, Down, Up)
// In your widget Build method
button := NewMouseRegion(buttonBounds)
button.OnClick = func(e *input.MouseEvent) {
fmt.Println("Button clicked!")
// Perform action
}router := input.NewInputRouter()
kb := router.GetKeyBindingManager()
// File operations
kb.AddBinding(input.KeyS, input.ModifierCtrl, saveFile)
kb.AddBinding(input.KeyO, input.ModifierCtrl, openFile)
kb.AddBinding(input.KeyN, input.ModifierCtrl, newFile)
// Edit operations
kb.AddBinding(input.KeyZ, input.ModifierCtrl, undo)
kb.AddBinding(input.KeyY, input.ModifierCtrl, redo)
kb.AddBinding(input.KeyC, input.ModifierCtrl, copy)
kb.AddBinding(input.KeyV, input.ModifierCtrl, paste)
kb.AddBinding(input.KeyX, input.ModifierCtrl, cut)region := input.NewMouseRegion(bounds)
var dragStart *goflow.Offset
region.OnMouseDown = func(e *input.MouseEvent) {
dragStart = e.Position
}
region.OnMouseDrag = func(e *input.MouseEvent) {
if dragStart != nil {
delta := &goflow.Offset{
X: e.Position.X - dragStart.X,
Y: e.Position.Y - dragStart.Y,
}
fmt.Printf("Dragging: %v\n", delta)
}
}
region.OnMouseUp = func(e *input.MouseEvent) {
dragStart = nil
fmt.Println("Drag ended")
}recognizer := router.GetGestureRecognizer()
var currentZoom float64 = 1.0
recognizer.SetOnPinch(func(gesture *input.MultiTouchGesture) {
currentZoom *= gesture.Scale
fmt.Printf("Zoom level: %.2f\n", currentZoom)
// Update your view's zoom
})Run the input test suite:
cd examples/input-test
./build-app.sh
open InputTest.appThe test app validates:
- ✅ Mouse click detection
- ✅ Mouse move tracking
- ✅ Mouse drag detection
- ✅ Keyboard key press/release
- ✅ Key code mapping
- ✅ Window resize events
The input system is designed for minimal overhead:
- Event dispatching: < 1μs per event
- Mouse region hit testing: O(n) where n = number of regions
- Keyboard state lookup: O(1) hash map
- Gesture recognition: Incremental updates, no full recalculation
- All input events are dispatched on the main thread
InputRouteris thread-safe for event routing- Platform backends use
runtime.LockOSThread()for UI thread affinity - Event callbacks should be non-blocking or dispatch to background threads
- Focus management system
- Text input method editor (IME) support
- Clipboard integration
- Accessibility events
- Stylus/pen input
- Game controller support
- macOS (Cocoa/NSEvent)
- Windows (Win32 API)
- Linux (X11/Wayland)
- Web (WASM + Canvas events)
Problem: Events not firing when clicking/typing
Solution:
- Ensure
runtime.LockOSThread()is called ininit() - Verify
macos.Run()is called (blocks and runs event loop) - Check that window is frontmost application
Problem: Key codes different than expected
Solution:
- macOS uses hardware key codes (position-based)
- Use the key code reference or test with input-test app
- Consider using key binding manager instead of raw codes
Problem: Mouse position not matching visual location
Solution:
- Coordinates are in view space (flipped Y-axis on macOS)
- Check if window scaling/DPI is applied
- Verify coordinate transformation in your render code
See full API documentation:
- pkg/input/README.md - Input system API
- backends/macos/README.md - macOS backend
- examples/input-test/ - Test application
To add a new input feature:
- Define event structure in
pkg/input/event.go - Add platform implementation in
backends/{platform}/ - Wire up to InputRouter
- Add tests in
examples/input-test/ - Update this documentation
See LICENSE for details.