Class Element
- Direct Known Subclasses:
ComponentElement,ContainerElement,GridViewElement,RenderObjectElement
An Element is a persistent object: when a Widget rebuilds, its Element is reused if the type still matches. That reuse is the key to the framework's efficient updates.
Element's responsibilities:
- Holds a reference to the Widget (updated across rebuilds)
- Manages the child Element tree
- Coordinates Widget creation, update, and disposal
- Supplies a RenderNode for RenderObjectElement
Element types:
ComponentElement- composite, manages a child Widget (StatelessWidget/StatefulWidget)RenderObjectElement- render, corresponds to an actual RenderNode
- Since:
- 6.2.0
- Version:
- 1.0.0
- Author:
- UltiTools Team
- See Also:
-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected voidAdds a child Element.voidassignContext(@NotNull BuildContext context) Assigns a Context to this Element (only used for the root Element or in tests).booleanChecks whether this Element can update to the given Widget.voidClears the dirty flag.private static booleancontainsIdentity(@NotNull List<Element> elements, @NotNull Element target) private @NotNull ElementcreateAndMountChild(@NotNull Widget newWidget) Creates the Element fornewWidgetand mounts it under this Element -- the single creation path for every child, whether it is new, a replacement, or part of a layout element's first mount.Gets the list of child Elements.@NotNull BuildContextGets the build context.@Nullable ElementGets the parent Element.@NotNull WidgetGets the Widget associated with this Element.booleanisDirty()Checks whether this Element needs a rebuild.booleanChecks whether this Element is mounted.voidmarkChildNeedsBuild(@NotNull Element child) Marks a child Element as needing a rebuild.voidMarks this Element as needing a rebuild.voidMounts this Element into the tree.abstract voidPerforms a rebuild.voidsetRootBuildScheduler(@Nullable Runnable scheduler) Registers, on the mounted root Element, the callback that a "needs rebuild" bubble hands off to onGuiRenderer.toString()voidunmount()Removes this Element from the tree.voidUpdates this Element with a new Widget.protected @Nullable ElementupdateChild(@Nullable Widget newWidget, @Nullable Element oldChild) Updates a single child Element.protected voidupdateChildren(@NotNull List<Element> ownedChildren, @NotNull List<Widget> newWidgets) Reconciles the caller's owned child Elements against a list of new Widgets, pairing bySlotKeywhere a Widget declares one and falling back to positional (index) pairing where it does not -- so a caller that never supplies a key sees exactly the same index-based behaviour this method replaces.
-
Field Details
-
_widget
-
_parent
-
_children
-
_dirty
private boolean _dirty -
_mounted
private boolean _mounted -
_context
-
_rootBuildScheduler
-
-
Constructor Details
-
Element
-
-
Method Details
-
assignContext
Assigns a Context to this Element (only used for the root Element or in tests).- Parameters:
context- the BuildContext
-
mount
Mounts this Element into the tree.Called the first time the Element is inserted into the tree. Subclasses should perform initialization work here and mount their child Elements.
CR-01 (T-05-62 / #371): every
mount(Element)override in this codebase callssuper.mount(parent)before doing its own work (mounting children, running the firstperformRebuild(), etc.) -- this base method is therefore the single choke point through which the ENTIRE mounting recursion passes, one call per Element, regardless of subtype:ContainerElement/GridViewElement.mountChildren()mounting each child throughupdateChildren()andupdateChild(), and themount()->performRebuild()->updateChild()->mount()cascadeStatelessElement/StatefulElementdrive when composing a child Widget for the first time. Checking the depth HERE, rather than separately in every subclass's own mounting method, guards the mount recursion exactly once per node without needing to duplicate the check at each override.update()/unmount()'s own recursions need no separate guard: they only ever walk a subtree whose depth was already validated here when it was first mounted, so they can never exceedRenderDepthGuard.MAX_DEPTHwithout first passing through an unguardedmount()call -- which this check closes.- Parameters:
parent- the parent Element, or null if this is the root Element
-
update
Updates this Element with a new Widget.Called when the Widget rebuilds. Subclasses should update their reference to the Widget and perform any necessary updates.
- Parameters:
newWidget- the new Widget instance
-
canUpdate
Checks whether this Element can update to the given Widget.- Parameters:
newWidget- the new Widget- Returns:
- true if it can be updated
-
unmount
public void unmount()Removes this Element from the tree.Called when the Element is no longer needed. Subclasses should perform cleanup work here and unmount their child Elements.
-
markNeedsBuild
public void markNeedsBuild()Marks this Element as needing a rebuild.This schedules the framework to rebuild this Element on the next frame.
-
markChildNeedsBuild
Marks a child Element as needing a rebuild.- Parameters:
child- the child Element
-
setRootBuildScheduler
Registers, on the mounted root Element, the callback that a "needs rebuild" bubble hands off to onGuiRenderer.Framework-internal method -- only GuiRenderer calls this, when it mounts or remounts the root Element; module authors do not need to (and should not) call it themselves. On a non-root Element this field is always null, because bubbling forwards to the first ancestor with
_parent != nulland never reaches this branch.- Parameters:
scheduler- the callback invoked when the root needs a rebuild, typicallyGuiRenderer::scheduleBuild; passnullto unregister (when the renderer is destroyed)
-
performRebuild
public abstract void performRebuild()Performs a rebuild.Subclasses should implement this method to build or rebuild the Widget tree.
-
getWidget
Gets the Widget associated with this Element.- Returns:
- the Widget instance
- Throws:
IllegalStateException- if the Widget is null
-
getParent
Gets the parent Element.- Returns:
- the parent Element, or null at the root
-
getChildren
Gets the list of child Elements.- Returns:
- the child Element list, or an empty list if there are none
-
getContext
Gets the build context.- Returns:
- the BuildContext
-
isMounted
public boolean isMounted()Checks whether this Element is mounted.- Returns:
- true if mounted
-
isDirty
public boolean isDirty()Checks whether this Element needs a rebuild.- Returns:
- true if it needs a rebuild
-
clearDirty
public void clearDirty()Clears the dirty flag. -
addChild
Adds a child Element.- Parameters:
child- the child Element
-
updateChild
@Nullable protected @Nullable Element updateChild(@Nullable @Nullable Widget newWidget, @Nullable @Nullable Element oldChild) Updates a single child Element.This is the core method of reconciliation. It decides whether to reuse the existing Element or create a new one.
- Parameters:
newWidget- the new WidgetoldChild- the previous child Element, may be null- Returns:
- the updated Element
-
createAndMountChild
Creates the Element fornewWidgetand mounts it under this Element -- the single creation path for every child, whether it is new, a replacement, or part of a layout element's first mount.A child becomes owned by its parent only when this method returns normally. If
mountthrows (for example aState.initStatethat subscribed to something and then failed, or a failing descendant of a newly created layout element), the new Element is unmounted here immediately, so whatever it or its descendants acquired is released and nothing is left mounted without an owner. The original exception then propagates unchanged; if the cleanup itself throws, that failure is attached to it withThrowable.addSuppressed(Throwable)rather than replacing it.- Parameters:
newWidget- the Widget to create and mount a child for- Returns:
- the mounted child Element
-
updateChildren
protected void updateChildren(@NotNull @NotNull List<Element> ownedChildren, @NotNull @NotNull List<Widget> newWidgets) Reconciles the caller's owned child Elements against a list of new Widgets, pairing bySlotKeywhere a Widget declares one and falling back to positional (index) pairing where it does not -- so a caller that never supplies a key sees exactly the same index-based behaviour this method replaces.This is the shared implementation D-09 item 5 asks for:
ContainerElementandGridViewElementwere literal twins, each hand-rolling the sameMath.min(size)index pairing and never readingWidget.getKey(). Both now call this method instead of maintaining their own copy, so the two classes cannot drift apart the way they did before this plan.Algorithm: the old child Elements are first split into a keyed group and an unkeyed group. Iterating the new Widget list, a keyed Widget looks up its match in the keyed group, and an unkeyed Widget takes the next available Element from the unkeyed group, in order. A match is reused via
updateChild(Widget, Element)(or replaced there if its type no longer matches); no match means a new Element is created. After the pass, whatever remains in either group -- unclaimed by any new Widget -- is unmounted.Ownership protocol.
ownedChildrenmust be the caller's own live, mutable child list; this method snapshots it first and rewrites its contents on every exit, so the caller never has to copy a result back:- On normal completion the list becomes exactly the reconciled children, in
newWidgets' order. - When a child's update, replacement or creation throws, the list becomes every child
already produced for
newWidgets(in that order), followed by every old child this reconciliation had not released that is still mounted, in its previous relative order: the old child paired with the Widget being processed when the exception occurred, and every old child no Widget had claimed yet. A replaced or unmounted old child is never kept. The exception then propagates unchanged. Because every element the parent still lists is mounted and every mounted child is still listed, closing the GUI later disposes each owned State exactly once.
newWidgets, so the next frame reuses every kept child and its State. Limit: when the failing pairing is itself a replacement whose new child could not be mounted, that position has no element any more (the old child was already disposed and the failed new child was cleaned up), so on the next frame later unkeyed siblings pair by position exactly as after removing a child from the middle of an unkeyed list, and may be rebuilt with a new State. Give siblings aSlotKeywhen their State must survive structural changes; keyed siblings keep their identity.- Parameters:
ownedChildren- the caller's live, mutable list of currently owned child Elements, in their current order; rewritten in place on every exitnewWidgets- the new child Widgets, in their desired order
- On normal completion the list becomes exactly the reconciled children, in
-
containsIdentity
-
toString
-