Recommended Event Propagation module

Overview

A GUI application handles OS events such as keyboard, mouse, and system events. In general, application code tends to grow. Typically, the MVC (Model-View-Controller) pattern or its variants are useful for splitting the logic of a GUI application into modules.

The Model (the "M" in the pattern name) stores application data. The Model is usually the simplest part of a GUI application. For example, in a mind map editor, this code keeps the current state of the mind map (tree nodes, the selected node, etc.) and provides methods to change the mind map (insert a new node, add a character to a node, etc.).

The View ("V") renders the model's current state to the user. For example, the View in a mind map editor renders node text, borders, lines between nodes, the cursor, etc. The core logic of the View is simple: display the model's current state to the user.

The Controller ("C") receives user events, calls the Model to change its state, and instructs the View to display the updated state. For example, the Controller in a mind map editor draws the mind map, waits for events, and updates the mind map. The Controller is often implemented as a large switch statement with cases for each event type.

If your application uses two or more window controllers, additional rules are required to separate events between different windows.

Some non-recommended approaches:

Recommended approach:

Recommended event-propagation rules

For example, a mouse click may be handled by the controller itself or by a child controller. The controller can get the child window rectangle from the child controller and compare it to the click coordinates. If the click occurs in a child window, the controller can pass the event to the child controller and skip its own event handler.

Basic use of the event-propagation module

The core of the event-propagation module is the event channel between parent and child controllers. The parent controller creates an eventlink.EventLink:

eventLink := eventlink.New()

and links the child controller to run its Action method as a goroutine:

eventLink.Link(ctx, appFramer, childController)

The child controller must implement the Actor interface to work with eventlink.EventLink:

type Actor interface {
    Action(ctx context.Context, app App)
    Wait()
}

At any time, the parent controller can send an event to the child's channel:

eventLink.Chan() <- receivedEvent

On the child side, the long-running Action method receives events from the channel, handles or ignores them, and redraws the child state in its own or any other window. The eventlink.App interface provides access to events, child-window creation, etc.

func (a *SimpleActor) Action(ctx context.Context, app eventlink.App) {
    // ...
    for {
        if len(app.Chan()) == 0 {
            // draw
        }

        e, ok := ctxchan.Get(ctx, app.Chan())
        if !ok {
             return
        }

        switch ev := e.(type) {
            // ...
        }
    }
}

An eventlink.App can be used where an eventlink.AppFramer is required by eventlink.Link to create a new link inside the child controller.

The root eventlink.AppFramer need to be created from impress.Application instance:

var appFramer eventlink.AppFramer = eventlink.MainApp(application)

See the module documentation and example for details.

Shutdown scenarios

In typical use cases, child controllers are created at startup and run while the parent controller remains alive.

More complex coexistence scenarios are possible:

To implement complex shutdown scenarios: