Input
Reacting to a mouse and keyboard

Input is delivered to the widgets via a system.FrameEvent through the Queue field.

Some of the most common events in FrameEvent.Queue are:

The program can respond to these events however it likes - for example, by updating its local data structures or running a user-triggered action. The FrameEvent is special - when the program receives a FrameEvent, it is responsible for updating the display by calling the e.Frame function with an operation list representing the new state. These operations are generated immediately in response to the FrameEvent which is the main reason that Gio is known as an “immediate mode” GUI.

Event-processors, such as Click and Scroll from package gioui.org/gesture detect higher-level actions from individual click events.

To distribute input among multiple different widgets, Gio needs to know about event handlers and their configuration. However, since the Gio framework is stateless, there’s no direct way for the program to specify that.

Instead, some operations associate input event types (for example, keyboard presses) with arbitrary tags (interface{} values) chosen by the program. A program creates these operations when it’s processing the FrameEvent – input operations are operations like any other. In return, an event.Queue supplies the events that arrived since the last frame, separated by tag.

You can think about the tag as a unique key for a given input area. The Gio event router will associate input events on in that area with the tag provided for that area. Then you can get those events the next frame by supplying the same tag to event.Queue. Often widgets will encapsulate this event logic by supplying a pointer to their persistent state as the tag for their input area.

The following example demonstrates pointer input handling:

var tag = new(bool) // We could use &pressed for this instead.
var pressed = false

func doButton(ops *op.Ops, q event.Queue) {
	// Process events that arrived between the last frame and this one.
	for _, ev := range q.Events(tag) {
		if x, ok := ev.(pointer.Event); ok {
			switch x.Type {
			case pointer.Press:
				pressed = true
			case pointer.Release:
				pressed = false
			}
		}
	}

	// Confine the area of interest to a 100x100 rectangle.
	area := clip.Rect(image.Rect(0, 0, 100, 100)).Push(ops)
	// Declare the tag.
	pointer.InputOp{
		Tag:   tag,
		Types: pointer.Press | pointer.Release,
	}.Add(ops)
	area.Pop()

	defer clip.Rect{Max: image.Pt(100, 100)}.Push(ops).Pop()
	var c color.NRGBA
	if pressed {
		c = color.NRGBA{R: 0xFF, A: 0xFF}
	} else {
		c = color.NRGBA{G: 0xFF, A: 0xFF}
	}
	paint.ColorOp{Color: c}.Add(ops)
	paint.PaintOp{}.Add(ops)
}


It’s convenient to use a Go pointer value for the input tag, as it’s cheap to convert a pointer to an interface{} and it’s easy to make the value specific to a local data structure, which avoids the risk of tag conflict.

For more details take a look at gioui.org/io/pointer (pointer/mouse events) and gioui.org/io/key (keyboard events).

External input

A single frame consists of getting input, registering for input and drawing the new state:

window := app.NewWindow()
for e := range window.Events() {
	switch e := e.(type) {
	case system.DestroyEvent:
		// The window was closed.
		return e.Err
	case system.FrameEvent:
		// A request to draw the window state.
		ops := new(op.Ops)
		// Draw the state into ops based on events in e.Queue.
		draw(ops, e.Queue)
		// Update the display.
		e.Frame(ops)
	}
}

Let’s make the button change it’s position every second. We can use a select to wait for events from the window and the external source at the same time. We’ll use a Ticker as an example external change. Once we have modified the state we need to notify the window to retrigger rendering with w.Invalidate().

window := app.NewWindow()

changes := time.NewTicker(time.Second)
defer changes.Stop()

buttonOffset := float32(0.0)

ops := new(op.Ops)
for {
	select {
	case e := <-window.Events():
		switch e := e.(type) {
		case system.DestroyEvent:
			return e.Err
		case system.FrameEvent:
			ops.Reset()

			// Offset the button based on state.
			op.Offset(f32.Pt(buttonOffset, 0)).Add(ops)

			// Handle button input and draw.
			doButton(ops, e.Queue)

			// Update display.
			e.Frame(ops)
		}

	case t := <-changes.C:
		buttonOffset = float32(t.Second()%3) * 100
		window.Invalidate()
	}
}


Writing a program using these concepts could get really verbose, which is why Gio provides standard widgets for common look and behaviour. Most programs end up using widgets primarily and few low-level operations.