-
Notifications
You must be signed in to change notification settings - Fork 0
Updating
First, all updates that might affect GUI display should be bracketed by the Ki
standard update calls:
updt := nb.UpdateStart()
// ... do updates
nb.UpdateEnd(updt)
The UpdateStart automatically marks the entire tree down from given node with the ki.Updating
flag, if it doesn't already have that flag set by virtue of some higher-level (or prior) UpdateStart call. The updt
return value is true
if this represents a novel update start for this node, and false if it is already within another update window. The assumption here is that updates are almost always nested structurally and over time, so that the first to call UpdateStart
will also be the last to call UpdateEnd
.
If the UpdateEnd can happen as the last call at the end of a method, it is a good idea to use the defer
syntax so you don't forget:
updt := nb.UpdateStart()
defer nb.UpdateEnd(updt)
// ... do updates
When UpdateEnd
is called, it emits an Updated
signal on its NodeSig
signal, if updt == true
(and resets all the Updating flags before that too).
Thus, to actually have the update do something, someone must be listening to that NodeSig
signal!
The someone who is listening for updates is typically the Viewport2D
for standard 2D nodes (e.g., Widget
s). When a node is Render2D
d, it connects to the Viewport, and if it is not visible, it disconnects from the Viewport, so that only visible nodes can trigger updates.
Thus, all of the logic for how to update a node is contained in the Viewport, which makes sense because nodes render into the Viewport which has shared RenderState and the image on which to render, so the viewport can regulate these updates and serialize them etc.
The function triggered by the update signal is: SignalViewport2D
in gi/viewport.go
, which then figures out what kind of update is required:
-
A full re-render of the tree (i.e., re-sizing, layout, and then render) for structural changes or if the node has been marked as requiring a full re-render using the NeedsFullReRender flag. If it has an anchor parent flagged with
ReRenderAnchor
flag (e.g., aFrame
or aSplitView
) then re-rendering occurs just under the level of that anchor node -- otherwise it is the entire tree under the viewport (see below for more info). -
Just a basic
Render2D
update of the node itself, e.g., if its state has changed. This is what happens when you mouse over a button for example -- it is very efficient and localized. -
Although most nodes should already know when they need a full re-render, you can manually call
SetFullReRender()
to ensure a full re-render if the automatic detection is not working properly -- after trying various more automated ways of doing this, it seems better to just manually add theseSetFullReRender()
calls in the relatively few cases where they are needed -- automation can only be so smart..
If you are likely to be driving a large number of repeated updates in a short period of time ("spamming" the updates) then it is a good idea to check the IsUpdatingNode()
method on the parent viewport, and don't do the update if it is already in the middle of an update. This will increase the overall responsiveness of the interface.
The ReRenderAnchor flag is almost always used on a gi.Frame
object, when it is a container of potentially dynamically-changing content that requires full re-rendering. By default any Frame in a SplitView
or a TabView
frame is marked as a ReRenderAnchor. If you have further more fine-grained areas in the display that contain dynamic content, definitely mark them with SetReRenderAnchor()
method.
For most cases, the GUI updates all occur within the same goroutine as the gi.Window
event loop (see Events), and thus there is generally no need to do anything special beyond the basic update blocking shown above.
However, if you do have another goroutine that is driving structural updates to the scenegraph, such that an update triggered on the main update thread might attempt to render a partially-formed scenegraph, then it it may be useful to call the BlockUpdates
and UnblockUpdates
methods on the parent Viewport2D
around these structural changes. This prevents the viewport from updating during this time. Care must also be taken not to have these blocks be called when updating during an existing viewport update, as that will then block indefinitely waiting to lock the mutex that has already been locked at the start of the update, so you need to distinguish between those two contexts.
The gi.Window
has a special ki.OnlySelfUpdate
flag set, which means that it only deals with itself for UpdateStart / End calls and does not propagate that state down the whole tree. Furthermore, the Update signal on the window is what triggers the final Publish
that blits the updated pixels out to the actual OS window so the user can see them. The Viewport typically manages the job of triggering that update.
If you are doing something that will trigger multiple Update actions on different nodes at the same level, which is not covered by a higher-level UpdateStart (e.g., updating the state of multiple actions in a toolbar), then it is much faster to block the final window-level Publish updates during those individual updates, by adding an UpdateStart / End bracket around the full set of updates at the top-level:
wupdt := tv.TopUpdateStart()
defer tv.TopUpdateEnd(wupdt)