java.lang.Object
com.ultikits.ultitools.abstracts.gui.declarative.core.Element
Direct Known Subclasses:
ComponentElement, ContainerElement, GridViewElement, RenderObjectElement

public abstract class Element extends Object
Element is the instantiated representation of a Widget, responsible for managing the Widget's lifecycle and updates.

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:

Since:
6.2.0
Version:
1.0.0
Author:
UltiTools Team
See Also:
  • Field Details

    • _widget

      @Nullable private @Nullable Widget _widget
    • _parent

      @Nullable private @Nullable Element _parent
    • _children

      @Nullable private @Nullable List<Element> _children
    • _dirty

      private boolean _dirty
    • _mounted

      private boolean _mounted
    • _context

      @Nullable private @Nullable BuildContext _context
    • _rootBuildScheduler

      @Nullable private @Nullable Runnable _rootBuildScheduler
  • Constructor Details

    • Element

      protected Element(@NotNull @NotNull Widget widget)
  • Method Details

    • assignContext

      public void assignContext(@NotNull @NotNull BuildContext context)
      Assigns a Context to this Element (only used for the root Element or in tests).
      Parameters:
      context - the BuildContext
    • mount

      public void mount(@Nullable @Nullable Element parent)
      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 calls super.mount(parent) before doing its own work (mounting children, running the first performRebuild(), 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 through updateChildren() and updateChild(), and the mount()->performRebuild()->updateChild()->mount() cascade StatelessElement/StatefulElement drive 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 exceed RenderDepthGuard.MAX_DEPTH without first passing through an unguarded mount() call -- which this check closes.

      Parameters:
      parent - the parent Element, or null if this is the root Element
    • update

      public void update(@NotNull @NotNull Widget newWidget)
      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

      public boolean canUpdate(@NotNull @NotNull Widget newWidget)
      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

      public void markChildNeedsBuild(@NotNull @NotNull Element child)
      Marks a child Element as needing a rebuild.
      Parameters:
      child - the child Element
    • setRootBuildScheduler

      public void setRootBuildScheduler(@Nullable @Nullable Runnable scheduler)
      Registers, on the mounted root Element, the callback that a "needs rebuild" bubble hands off to on GuiRenderer.

      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 != null and never reaches this branch.

      Parameters:
      scheduler - the callback invoked when the root needs a rebuild, typically GuiRenderer::scheduleBuild; pass null to 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

      @NotNull public @NotNull Widget getWidget()
      Gets the Widget associated with this Element.
      Returns:
      the Widget instance
      Throws:
      IllegalStateException - if the Widget is null
    • getParent

      @Nullable public @Nullable Element getParent()
      Gets the parent Element.
      Returns:
      the parent Element, or null at the root
    • getChildren

      @NotNull public @NotNull List<Element> getChildren()
      Gets the list of child Elements.
      Returns:
      the child Element list, or an empty list if there are none
    • getContext

      @NotNull public @NotNull BuildContext 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

      protected void addChild(@NotNull @NotNull Element child)
      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 Widget
      oldChild - the previous child Element, may be null
      Returns:
      the updated Element
    • createAndMountChild

      @NotNull private @NotNull Element createAndMountChild(@NotNull @NotNull Widget newWidget)
      Creates the Element for newWidget and 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 mount throws (for example a State.initState that 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 with Throwable.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 by SlotKey where 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: ContainerElement and GridViewElement were literal twins, each hand-rolling the same Math.min(size) index pairing and never reading Widget.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. ownedChildren must 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.
      In the common failure shape (a child was replaced, then a later sibling's update threw) the kept list lines up position for position with 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 a SlotKey when 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 exit
      newWidgets - the new child Widgets, in their desired order
    • containsIdentity

      private static boolean containsIdentity(@NotNull @NotNull List<Element> elements, @NotNull @NotNull Element target)
    • toString

      public String toString()
      Overrides:
      toString in class Object