-
Notifications
You must be signed in to change notification settings - Fork 0
Events
The oswin
OS-specific window system drivers send Mouse, Key, Window etc events to the gi.Window
, which then sends those events on to the specific widgets.
Each widget registers to receive specific types of events from the window using the ConnectEvent
method, and typically includes a function right there that processes the event. e.g., here is the event processing call for a Button MouseEvent:
// MouseEvents handles button MouseEvent
func (bb *ButtonBase) MouseEvent() {
bb.ConnectEvent(oswin.MouseEvent, RegPri, func(recv, send ki.Ki, sig int64, d interface{}) {
me := d.(*mouse.Event)
bw := recv.(ButtonWidget)
bbb := bw.ButtonAsBase()
if me.Button == mouse.Left {
switch me.Action {
case mouse.DoubleClick: // we just count as a regular click
fallthrough
case mouse.Press:
me.SetProcessed()
bbb.ButtonPressed()
case mouse.Release:
me.SetProcessed()
bw.ButtonRelease()
}
}
})
}
The ConnectEvents2D
method is the standard Node2D
interface method that is called to register these event processing methods, during the Render2D
call, only if the node is actually visible. It is good practice to create separate type-specific methods for the different kinds of event processing (or multiple if they always should go together) so that derived types can re-use those event processors, while also adding their own as needed. Here's what that looks like on ButtonBase:
func (bb *ButtonBase) ButtonEvents() {
bb.HoverTooltipEvent()
bb.MouseEvent()
bb.MouseFocusEvent()
bb.KeyChordEvent()
}
func (bb *ButtonBase) ConnectEvents2D() {
bb.ButtonEvents()
}
func (bb *ButtonBase) Render2D() {
if bb.FullReRenderIfNeeded() {
return
}
if bb.PushBounds() {
bb.This().(Node2D).ConnectEvents2D()
...
} else {
bb.DisconnectAllEvents(RegPri)
}
}
From the end-user app perspective, everything is asynchronously event-driven through the event processing functions as shown above. E.g., someone hits a button and that ends up calling relevant code..
Under the hood, the main thread is running an event loop that processes the oswin OS-driven events (you can also run code on this main thread using oswin.TheApp.RunOnMain()
), and then each window is running its own separate goroutine for its own event loop, where it reads from its own event channel and then sends those out to the widgets.
The widget event calls are made in the same goroutine as the window event loop, so they are blocking and sequential on that thread of processing. Thus, any more significant processing triggered by an event should generally call go
at some point to fork off into a separate goroutine. Once that occurs, then event processing should proceed in parallel through the go scheduler continuing to run the different event processing loops. Also you definitely do not want to run anything long and blocking on the oswin.TheApp.RunOnMain()
as that will block all gui events.
Another alternative for longer-running functions is to periodically call PollEvents()
on the window, which will process any currently-waiting events but will not wait for new ones. This first calls PollEvents for the system events, and then handles any events at the gi.Window
level.
See Updating for important notes about coordinating updates from a separate go routine.
When a main
program starts up, the last call is typically win.StartEventLoop()
which then runs until the window is closed, at which point execution will return to the next line after that call. All other windows should be opened using win.GoStartEventLoop()
which just runs the loop using go
in a separate routine.
If multiple different windows can be opened and execution only terminates after the last one is closed, then start all event loops using win.GoStartEventLoop()
to run on separate goroutines, and use this call:
gi.WinWait.Wait()
WinWait is a sync.WaitGroup
that gets added to with the GoStartEventLoop call.