Class Tree<T>

java.lang.Object
limn.scene.Widget<Tree<T>>
limn.components.tree.Tree<T>
All Implemented Interfaces:
Scrollable

public final class Tree<T> extends Widget<Tree<T>> implements Scrollable
An outline over children the application provides: one column of rows at a depth, each with an indent, a disclosure triangle where it can open, and the application's own cell widget.

The application keeps its nodes. A tree is handed a Tree.Model, which answers what the roots are, what a node's children are, and what widget draws one. Nothing here is a node type of this toolkit's: a tree over Path, over a record, or over a live domain object is the same tree. Identity is the node's own equals, which gives value semantics to records and reference semantics to mutable objects, and both are what the application that chose them wanted. A node is unique within a tree: two equal nodes in two places would share one selection, one expansion and one accessible identity, so the tree refuses them the moment both are visible, with an IllegalStateException naming the node. A model whose values repeat under different parents — a file named the same in two folders — gives its nodes path identity, the way the guide's example does.

A row may promise children before it can name them. Tree.Model.children(T) answers null for a node whose children are not known yet, and Tree.Model.load(T) hands back a Work that fetches them: the row opens, shows that it is working, and fills in when the job lands on the UI thread; its spinner steps four times a second instead of turning smoothly while the user has asked the system for less motion. Collapsing a row that is still loading cancels the job, because a result nobody is looking at is a result nobody should pay for. A directory that has not been read is therefore not a leaf — it has a triangle, and pressing it is what reads it — and one that turns out to hold nothing stays open, with a muted "Empty" line where its children would be.

Rows are realized where the viewport reaches, by the anchor-and-walk this toolkit's list and table already use: a row's height is measured when it is first needed, the mean seeds the scroll estimate (never the tree's own preferred height, which is setVisibleRows(int) seed rows under an unbounded parent), and two rows are kept mounted even when a scroll carries them outside: the one holding the keyboard focus, and the cursor row while the tree itself holds the keyboard, so a reader's cursor survives a wheel, a refresh and a reorder. The order rows are walked in is a traversal of what is expanded, which is the one thing a tree does that a list cannot.

What this is not: no columns — a TreeTable is a widget still to come — no in-place editing, ever, no drag to reorder, and no tri-state checkbox cascade over a data model the toolkit does not own.

The cursor is not the selection. The row the keyboard is on (cursorNode()) moves with the arrows in every mode and is what Enter activates; the selection (selectedNodes(), led by leadNode()) follows it where the mode allows, and in MULTI a row toggled off keeps the cursor. Table keeps the same pair. While the tree holds the keyboard the cursor row's cell wears a thin focus ring, which is how the cursor is seen where the selection wash is not on it.

To a screen reader this is a TREE of TREE_ITEMs, each carrying its expanded state, its selection numbered among its siblings ("2 of 5"), and its depth and flat row index through the hierarchy facet ("level 3"), the cursor row ACTIVE while the tree holds the keyboard. All three platform bridges carry those numbers, and macOS also answers the disclosure attributes VoiceOver reads an outline row by.

  • Field Details

  • Constructor Details

    • Tree

      public Tree(Tree.Model<T> model)
      A tree over model, which supplies the nodes and the widgets that draw them.
  • Method Details

    • refresh

      public void refresh()
      Re-reads the model from the roots down, keeping what is expanded and dropping every cached child list: the application changed its data and the tree owns none of it. An open row whose children the model has to fetch therefore fetches them again, cancelling a load still in flight for it. UI thread only.

      What the tree remembers about a node — that it is selected, that it is open, that the cursor stands on it — outlives the refresh as long as the model still has the node. A node that is a row afterwards is confirmed by that; one that is not (under a closed branch, or under an open row whose children are being fetched again) is verified along the path it was last seen at, and only that path: a step the model no longer has drops the node, a step whose children are still on their way keeps it until they land, when it is verified again and dropped one announcement later if gone, and a step whose children nobody has fetched keeps it, since confirming it would mean a load nobody asked for. The identifiers of what the model no longer has are released with it.

      A row a popup hangs from (a context menu opened on its cell, a popover) is not rebuilt while its node is still a row: its cell stays as it is, at the node's row now, so the popup stays open through a refresh a timer makes, and is built afresh at the first layout after the popup closes. The popup acts on the node the cell was built for, as the model gave it: a model that hands out new node objects equal to the old ones leaves it acting on the old object, so a menu that can outlive a refresh acts through the node's key. When the node is no longer a row the cell is let go of and the popup closes with it, and so is a cell the keyboard is in or returns to when its popup closes, such as a field whose own menu is open: kept, it would stay bound to the node it was built for, and an edit made in it would go there.

    • isExpanded

      public boolean isExpanded(T node)
      Returns:
      whether node is open
    • expand

      public Tree<T> expand(T node)
      Opens node, loading its children if the model has to fetch them. UI thread only.
    • collapse

      public Tree<T> collapse(T node)
      Closes node, cancelling a load still in flight for it. UI thread only.
    • visibleRowCount

      public int visibleRowCount()
      Returns:
      how many nodes are visible, which is a traversal of what is open. The "Loading…" line under a row whose children are on their way is not one of them.
    • setScrollbarPolicy

      public Tree<T> setScrollbarPolicy(ScrollBar.Policy policy)
      Sets when the scroll bars are shown (default ScrollBar.Policy.AUTO), the table's setter under the table's name.
      Parameters:
      policy - the policy
      Returns:
      this tree
    • setBarLayout

      public Tree<T> setBarLayout(ScrollGutters.Layout layout)
      Sets whether the bars float over the rows or reserve strips of their own (default ScrollGutters.Layout.OVERLAY), the table's setter under the table's name.

      Reserved is what a tree whose cells end in something usually wants: a count, a badge or a button against a row's trailing edge is exactly what a thumb covers. The strips key on overflow, so a deep tree takes the horizontal one only once it scrolls sideways.

      Parameters:
      layout - the layout
      Returns:
      this tree
    • barLayout

      public ScrollGutters.Layout barLayout()
      Whether the scroll bars overlay the rows or reserve strips beside them.
    • setVisibleRows

      public Tree<T> setVisibleRows(int rows)
      Sets how many seed rows tall the tree prefers to be when its parent gives it no height (default 8, the table's number): inside a column or a scroll pane, that is its height. A count of the size step's seed row, never of the rows realized, so the preference stands whatever scrolls in. A bounded height from the parent wins. UI thread only.
      Parameters:
      rows - the count, at least one
      Returns:
      this tree
      Throws:
      IllegalArgumentException - if rows is below one
    • visibleRows

      public int visibleRows()
      How many seed rows tall this tree prefers to be under an unbounded height.
    • setSelectionMode

      public Tree<T> setSelectionMode(SelectionMode mode)
      How many rows may be selected at once; the default is SelectionMode.SINGLE. The cursor stays where it is in every mode: NONE empties the selection, not the row the keyboard is on.
    • selectionMode

      public SelectionMode selectionMode()
      Returns:
      how many rows the user may select: none, one, or any number
    • selectedNodes

      public List<T> selectedNodes()
      Returns:
      the selected nodes, in the order they were selected
    • cursorNode

      public @Nullable T cursorNode()
      Returns:
      the row the keyboard is on, or null before the keyboard was in the tree: what the arrows move, what Enter activates, and the row a reader's cursor stands on. It moves in every mode, SelectionMode.NONE included, and is not the selection; see leadNode() for that.
    • leadNode

      public @Nullable T leadNode()
      Returns:
      the selection's lead — the node the user selected last that is still selected — or null when nothing is selected. Until 2026-09-14 this was also the row the keyboard was on, which cursorNode() answers now: the two part in MULTI, where a row toggled off keeps the cursor and loses the lead, and in NONE, where the cursor moves and this is always null.
    • setSelected

      public Tree<T> setSelected(@Nullable T node)
      Selects exactly node, moving the cursor onto it and revealing it when it is a visible row. A node under a closed branch is selected where it is — re-opening its parent finds it selected — and neither revealed nor made the cursor, which stays on a row the user can see. null clears the selection and leaves the cursor where it is.
    • setSelectedNodes

      public Tree<T> setSelectedNodes(Collection<? extends T> nodes)
      Selects exactly nodes, in that order, with the last as the lead and under the cursor: what an application restoring a saved selection calls. In SelectionMode.SINGLE only one node may be named. Announces SELECTION/ CODE once when the set changed. UI thread only.
      Parameters:
      nodes - the nodes; none clears the selection
      Returns:
      this tree
      Throws:
      IllegalStateException - if the mode is SelectionMode.NONE, or SINGLE and more than one node is named
    • clearSelection

      public Tree<T> clearSelection()
      Drops the selection, announced as SELECTION/CODE; nothing happens when nothing is selected. The cursor stays where it is. UI thread only.
      Returns:
      this tree
    • selectAll

      public Tree<T> selectAll()
      Selects every open row, in SelectionMode.MULTI: what is visible, so a node under a closed branch is not taken. The lead is kept where it is in the selection, or becomes the first row; the cursor does not move. A caller's write, announced as SELECTION/CODE; Ctrl+A or Cmd+A enters the same seam as the user's. UI thread only.
      Returns:
      this tree
    • activate

      public void activate()
      Announces that the cursor row was opened, as INVOKED/CODE: a caller's verb, which reaches a watcher and not onActivate, the way Enter does. Nothing without a cursor row. UI thread only.
    • onSelect

      public Tree<T> onSelect(@Nullable Runnable handler)
      The application's response to the user changing the selection: a click, a key, an assistive technology's select. The selection is read back from selectedNodes(), and the lead from leadNode(); the handler takes no node because a selection is a set, and the node a toggle removed is not one to hand anybody. Never for setSelected(T), or a refresh() or setSelectionMode(limn.components.SelectionMode) that moved it, which are the caller's or the tree's own; to hear every change whatever caused it, watch the tree.
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this tree
      Throws:
      IllegalStateException - if a handler is already registered
    • onActivate

      public Tree<T> onActivate(@Nullable Consumer<T> handler)
      The application's response to the user opening a row: Enter, a double click, an assistive technology's press. Handed the row that was opened — the cursor row for Enter and a double click, which in NONE is a row that was never selected, and the addressed row for a reader's PRESS, which need not be the cursor's. Never for activate(), which is a caller's verb.
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this tree
      Throws:
      IllegalStateException - if a handler is already registered
    • onExpand

      public Tree<T> onExpand(@Nullable Consumer<T> handler)
      Runs when the user opens a row.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • onCollapse

      public Tree<T> onCollapse(@Nullable Consumer<T> handler)
      Runs when the user closes a row.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      Description copied from class: Widget
      Runs this widget's application handler for a change it has just announced. Called by Widget.runHandler(limn.scene.Change), and so by Widget.notifyChange(limn.scene.Change), only for a USER origin and after every watcher has run.

      A component overrides it once and switches on the aspect, reading its own accessors for the payload: a slider passes value(), a combo box selectedIndex(), a text field text(). A component with no handler does not override it.

      An override that does not recognise the aspect must call super, because a subclass of a component that has a handler inherits that component's dispatch through this method: a SearchField that overrode this for its own aspect and did not chain would silently delete TextField's. The ordering lives in the base class, this is the one link in it a subclass can break, and the contract test drives every subclass through its parent's gestures to catch one that does.

      Overrides:
      handleUserChange in class Widget<Tree<T>>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.

      Resolve the ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget<Tree<T>>
    • setEmptyContent

      public Tree<T> setEmptyContent(@Nullable Widget<?> content)
      Shows content in the rows' viewport whenever the tree has no rows (a tree with no root rows), and takes it away when one arrives: the tree knows its count, so the application never says when. It gives the words, "No orders" or "No orders match", which only it knows.

      The content is the tree's last child while it shows, so its own controls (a "Clear filter" button) follow the tree in the Tab order and a reader finds them after its (no) rows; they belong to no selection. It is this tree's own: one already in another container is refused, and one given to two widgets shows in one only. A content passed this way describes nothing to a reader: pass its message with setEmptyContent(Widget, Label).

      Parameters:
      content - the content, or null for none
      Returns:
      this tree
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this tree
    • setEmptyContent

      public Tree<T> setEmptyContent(@Nullable Widget<?> content, @Nullable Label message)
      Shows content while there are no rows, as setEmptyContent(Widget) does, and describes the tree by message while it shows, so a reader landing on it hears "tree, 0 rows" and then the message.
      Parameters:
      content - the content, or null for none
      message - the label inside it that says why there are no rows, or null
      Returns:
      this tree
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this tree
    • setEmptyMessage

      public Tree<T> setEmptyMessage(@Nullable I18nString message)
      Shows one muted, wrapped, centred line while there are no rows, which also describes the tree to a reader: the empty content for words alone.
      Parameters:
      message - the words, or null for no empty content
      Returns:
      this tree
    • setEmptyMessage

      public Tree<T> setEmptyMessage(@Nullable String message)
      Shows one fixed line while there are no rows; see setEmptyMessage(I18nString).
      Parameters:
      message - the words, or null for no empty content
      Returns:
      this tree
    • clearEmptyMessage

      public Tree<T> clearEmptyMessage()
      Takes the message shown with no rows away, as setEmptyMessage(null) would: that call does not compile, because null fits more than one of the overloads.
      Returns:
      this tree
    • emptyContent

      public @Nullable Widget<?> emptyContent()
      Returns:
      the content shown while there are no rows, or null
    • isShowingEmptyContent

      public boolean isShowingEmptyContent()
      Returns:
      whether the empty content shows now, as the last layout placed it: there is one, no rows and no load running, and no other widget holds it
    • setLoading

      public Tree<T> setLoading(boolean loading)
      Says the tree is loading its rows, or has finished. While it loads with no rows, the loading content shows in the viewport in place of the empty one; over rows already shown, they stay. Either way the tree is published busy, and a reader is told both ends: "Loading Orders" at the start, with the name a reader hears the tree by (given by setAccessibleName, or by a caption bound with Label.setLabelFor(limn.scene.Widget<?>)), and at the end how many rows arrived, or the empty message when none did. A tree in a hidden container, on a tab page not chosen or on a face a transform does not draw is busy and says nothing of its load. Set the rows first, then say the load ended, so the count said is the count shown.
      Parameters:
      loading - whether a load is running
      Returns:
      this tree
    • isLoading

      public boolean isLoading()
      Returns:
      whether a load is running, as setLoading(boolean) last said
    • setLoadingContent

      public Tree<T> setLoadingContent(@Nullable Widget<?> content)
      Replaces what shows while the tree loads with no rows; null brings back the default, a moving bar over a muted "Loading…" in the tree's language.
      Parameters:
      content - the content, or null for the default
      Returns:
      this tree
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this tree
    • setLoadingContent

      public Tree<T> setLoadingContent(@Nullable Widget<?> content, @Nullable Label message)
      Replaces what shows while the tree loads with no rows, and describes the tree by message while it shows.
      Parameters:
      content - the content, or null for the default
      message - the label inside it that says what is loading, or null
      Returns:
      this tree
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this tree
    • isShowingLoadingContent

      public boolean isShowingLoadingContent()
      Returns:
      whether a loading content, the application's or the default, shows now, as the last layout placed it
    • onLayout

      protected void onLayout()
      Description copied from class: Widget
      Containers position children here (measure + Widget.layoutBox(float, float, float, float) per child).
      Overrides:
      onLayout in class Widget<Tree<T>>
    • scrollBy

      public void scrollBy(float dx, float dy)
      Scrolls by a delta in logical points: positive dy toward the last row, positive dx toward the trailing edge. A tree whose content fits its box has nothing to do sideways. UI thread only.
      Parameters:
      dx - how far sideways
      dy - how far along the rows
    • revealRect

      public void revealRect(float x, float y, float rectWidth, float rectHeight)
      Description copied from interface: Scrollable
      Scrolls the minimum amount so the rectangle becomes visible. The rectangle is given in this widget's local (viewport) coordinates, i.e. the same space a child's x()/y() resolve to. Rectangles larger than the viewport align their near edge. Implementations clamp to their content bounds; already-visible rects are a no-op. UI thread only.
      Specified by:
      revealRect in interface Scrollable
    • setDragEnabled

      public Tree<T> setDragEnabled(boolean enabled)
      Lets the user drag nodes out of this tree (default off): the selected nodes when the node pressed is one of them, and that node alone otherwise; a node goes with everything under it, so one under another that is dragged is not named twice. From the keyboard, the scene's drag key picks them up. What they carry is NODES and their text, or what setDragContent(java.util.function.Function<? super java.util.List<T>, limn.transfer.Transfer>) makes. A target is offered a copy; a move too once onReorder or onMoveOut is set, and a move first with the latter. A row's own controls and its triangle keep their gestures. UI thread only.
      Parameters:
      enabled - whether nodes can be dragged
      Returns:
      this tree
    • isDragEnabled

      public boolean isDragEnabled()
      Returns:
      whether nodes can be dragged
    • setDragContent

      public Tree<T> setDragContent(@Nullable Function<? super List<T>,Transfer> content)
      Says what dragged nodes carry, in place of NODES and their text. UI thread only.
      Parameters:
      content - makes the transfer for the nodes dragged, or null for the default
      Returns:
      this tree
    • onReorder

      public Tree<T> onReorder(@Nullable Tree.Move<T> handler)
      Lets the user move nodes within this tree. Dragged, when dragging is on: dropped on a node they go into it, near its upper or lower edge before or after it, never into themselves, and a closed node the pointer rests on opens. And by Alt+Shift with an arrow: Up and Down among its siblings, Left out one level, Right into the sibling before it. The handler moves the data; the tree then shows it, opens the node they went into and says where they are. UI thread only.
      Parameters:
      handler - moves the nodes, or null for a tree that does not rearrange
      Returns:
      this tree
    • onMoveOut

      public Tree<T> onMoveOut(@Nullable Consumer<? super List<T>> handler)
      Hears that nodes dragged out of this tree were moved to somewhere else, another widget or another program, and removes them. With it a drag proposes a move unless the user's modifier key asks for a copy; without it, a copy. UI thread only.
      Parameters:
      handler - removes the nodes, or null
      Returns:
      this tree
    • onDrop

      public Tree<T> onDrop(@Nullable Tree.DropHandler<T> handler)
      Lets things be dropped on this tree, into a node or between two: what carries one of the formats named, from another tree, a list, another program. UI thread only.
      Parameters:
      handler - takes a drop, or null for a tree that takes none
      Returns:
      this tree
    • setDropFormats

      public Tree<T> setDropFormats(DataFormat<?>... formats)
      Names what onDrop(DropHandler) takes: a drag carrying any of these is accepted. UI thread only.
      Parameters:
      formats - the formats taken; none takes nothing
      Returns:
      this tree
    • setDropAction

      public Tree<T> setDropAction(@Nullable DropAction action)
      Says what a drop from elsewhere does here, whatever the source would rather: a tree that only ever copies what it is given asks for DropAction.COPY, one that takes things over for DropAction.MOVE. It is what the drop does where the source allows it; where it does not, and by default, the drop does what the user is asking for, the action the modifier keys name or the source's own preference. Nodes moved within this tree are always a move. UI thread only.
      Parameters:
      action - the action asked for, or null for the one proposed
      Returns:
      this tree
    • dropAction

      public @Nullable DropAction dropAction()
      Returns:
      the action a drop from elsewhere is asked to be, or null for the one proposed
    • setDragWithinWindow

      public Tree<T> setDragWithinWindow(boolean within)
      Keeps a drag of this tree's nodes inside its window (default: not): no other program is offered them, and no other window. For nodes that mean nothing anywhere else. UI thread only.
      Parameters:
      within - whether a drag stays in the window it began in
      Returns:
      this tree
    • isDragWithinWindow

      public boolean isDragWithinWindow()
      Returns:
      whether a drag of this tree's nodes stays in its window
    • onDragStart

      protected @Nullable Drag onDragStart(DragStartEvent event)
      Description copied from class: Widget
      Asked when a drag may begin on this widget: the pointer was pressed here and has moved a few points, or the drag key was pressed while this widget had the focus. The question bubbles from the widget under the pointer, and the first Drag answered starts the drag; the press is then over for the widget that had it. Default: null, which leaves the gesture to the DRAG events it has always been.
      Overrides:
      onDragStart in class Widget<Tree<T>>
      Parameters:
      event - where the press began, and whether the keyboard asked
      Returns:
      what to drag, or null
    • onDragOver

      protected void onDragOver(DragEvent event)
      Description copied from class: Widget
      A drag is over this widget, or has moved on it. A widget that would take what is offered calls event.accept() and marks where the drop would land; one that says nothing passes the question to its parent, and may show that it refuses. The question goes to the widget under the pointer and then to each enabled ancestor in turn, up to the first that accepts. It is asked again on every move, so that the answer can depend on the place, and again when the content under a still pointer scrolls: a pointer drag resting near an edge of a scrolling view it is over, or past one, scrolls that view. In a keyboard drag only the widget the drag key, Tab or an arrow brings the drag to is asked. Files dragged in from the system arrive the same way, their offer holding DataFormat.FILES. Whatever this draws, accepting or not, Widget.onDragExit(limn.scene.event.DragEvent) takes away. Default: says nothing.
      Overrides:
      onDragOver in class Widget<Tree<T>>
      Parameters:
      event - what is offered and where
    • onDragExit

      protected void onDragExit(DragEvent event)
      Description copied from class: Widget
      The drag this widget was last asked about in Widget.onDragOver(limn.scene.event.DragEvent) is no longer over it, whether it accepted or not: whatever it drew for the drag goes, the mark of the drop's place or the sign that it refuses. It comes once, at the first of these: a move whose question no longer reaches this widget (the pointer left it, or a widget under the pointer accepted before it was asked); the drag called off, by Escape, another button, the window losing the focus or the source leaving; the drag leaving the window; the drop, on another widget or over this one with no widget taking it; this widget taken out of the scene, hidden or disabled while the drag is over it. In a keyboard drag a widget that does not accept hears it at once, the drag going on to the next, and the target when the drag moves on or ends. A drag from another window or program is told the same. Never after Widget.onDrop(limn.scene.event.DropEvent): the widget handed the drop hears no exit. Default: nothing.
      Overrides:
      onDragExit in class Widget<Tree<T>>
      Parameters:
      event - the drag as it leaves
    • onDrop

      protected void onDrop(DropEvent event)
      Description copied from class: Widget
      What was dragged is dropped here: on the widget that accepted it last. Read what is wanted from event.offer() and carry out event.action(); whatever marked the place goes now, since no Widget.onDragExit(limn.scene.event.DragEvent) follows. Default: nothing.
      Overrides:
      onDrop in class Widget<Tree<T>>
      Parameters:
      event - what was dropped and where
    • onCanEdit

      protected boolean onCanEdit(EditCommand command)
      Description copied from class: Widget
      Whether this widget can do an editing command now: copy with something selected, paste with something to paste, undo with something to take back. Scene.canEdit(limn.scene.EditCommand) asks the widget that has the keyboard and then each of its ancestors, and a menu's item of an editing role is enabled by the answer.

      A widget answers for the commands it takes from the keyboard, and does for the command what it does for the key.

      Overrides:
      onCanEdit in class Widget<Tree<T>>
      Returns:
      false here: a widget edits nothing until it says so
    • onEdit

      protected boolean onEdit(EditCommand command)
      Description copied from class: Widget
      Does an editing command, as the user's own act. Called on the first widget, from the one with the keyboard upward, whose Widget.onCanEdit(limn.scene.EditCommand) answered true.
      Overrides:
      onEdit in class Widget<Tree<T>>
      Returns:
      whether it was done
    • onKeyEvent

      protected void onKeyEvent(KeyEvent event)
      Description copied from class: Widget
      Key events (focused widget first, then ancestors).
      Overrides:
      onKeyEvent in class Widget<Tree<T>>
    • onMouseEvent

      protected void onMouseEvent(MouseEvent event)
      Description copied from class: Widget
      Mouse events (bubbling); call event.consume() when handled.
      Overrides:
      onMouseEvent in class Widget<Tree<T>>
    • onHitTest

      protected @Nullable Widget<?> onHitTest(float localX, float localY)
      Description copied from class: Widget
      What this widget answers for a point that reached it: one of its children's answers, itself, or null to let the point through to what is beneath it. Called by Widget.hitTest(float, float) only for a widget that is visible, enabled and in reach, with the point inside its box or where it draws a descendant moved past it, so an override checks none of that again.

      The default asks each child through Widget.hitTestChild(limn.scene.Widget<?>, float, float), the last first because it is painted on top, and answers the first that is hit, or this widget when none is. A widget overrides it to ask one child before the others (a scroll bar over the content it scrolls), to keep a part of itself from its children, or to be transparent to the pointer.

      Overrides:
      onHitTest in class Widget<Tree<T>>
      Parameters:
      localX - the point's x in this widget's space
      localY - the point's y in this widget's space
      Returns:
      the widget the point lands on, or null
    • clipsChildren

      protected boolean clipsChildren()
      Description copied from class: Widget
      Whether Widget.paintChildren(limn.graphics.Canvas) clips its children to this widget's bounds (scroll views, list viewports, tab strips). Partial rendering uses it to clamp a descendant's Widget.invalidate() damage to the visible region; a widget scrolled out of view damages nothing. Any override that clips in paintChildren should also override this, and one whose clip is smaller than its box for some child should override the four clip accessors below too.
      Overrides:
      clipsChildren in class Widget<Tree<T>>
    • paintChildren

      protected void paintChildren(Canvas canvas)
      Description copied from class: Widget
      Children pass; override to clip (e.g. scroll views).

      Indexed rather than an enhanced for, for the reason Scene's own overlay loop already carries: an iterator here is an allocation per widget per frame, and an idle window is the one place in this toolkit that is not allowed to allocate at all. A subtree of two hundred widgets was paying two hundred short-lived objects a frame to walk a list it can index.

      Overrides:
      paintChildren in class Widget<Tree<T>>
    • onAttached

      protected void onAttached()
      Description copied from class: Widget
      Called when this widget enters a scene (attached to the tree). Widget.scene() is the scene it just joined. Fires top-down, so a parent runs before its children. Default no-op.
      Overrides:
      onAttached in class Widget<Tree<T>>
    • onFocusGained

      protected void onFocusGained()
      Description copied from class: Widget
      Called when this widget takes keyboard focus. Default: nothing.
      Overrides:
      onFocusGained in class Widget<Tree<T>>
    • onFocusLost

      protected void onFocusLost()
      Description copied from class: Widget
      Called when this widget loses keyboard focus. Default: nothing.
      Overrides:
      onFocusLost in class Widget<Tree<T>>
    • onDetached

      protected void onDetached()
      Description copied from class: Widget
      Called when this widget leaves the scene: detached from the tree, moved into another scene (told this before the Widget.onAttached() of the scene it joins), or still in it when the scene's window closes, which detaches every widget the scene has. What was taken in Widget.onAttached() from something that outlives the window (a listener's place in a list of the process's, a resource of the system's) is given back here.

      Widget.scene() still answers the scene being left, and becomes null once this returns, so releasing something the scene owns needs no field of your own to remember it by. Fires bottom-up, so children run before their parent, and every one of them can still reach the scene.

      Release resources here, but GPU resources must be handed to Scene.disposeLater(limn.graphics.GpuSurface) (disposal needs the owning GL context, which is only current inside a frame). Not called on Widget.setVisible(boolean). Default no-op.

      Overrides:
      onDetached in class Widget<Tree<T>>
    • onAccessibility

      protected void onAccessibility(Accessibility a)
      The tree itself.

      TREE, since 2026-09-13, when the AT-SPI number the role was waiting on came off the Fedora guest. Until then it published LIST, because a role with no number announces as "invalid" on Linux and a list of items that can open was the truthful answer on all three platforms.

      Overrides:
      onAccessibility in class Widget<Tree<T>>
      Parameters:
      a - the node being described; write into it, and never keep it
    • onAccessibilityChildIdentity

      protected void onAccessibilityChildIdentity(Widget<?> child, Accessibility a)
      A row's identity is its node's stable identifier, answered before the cell describes itself so that a cell recycled to another node carries nothing of the old one.
      Overrides:
      onAccessibilityChildIdentity in class Widget<Tree<T>>
      Parameters:
      child - the child about to be described
      a - the builder, open only for the two identity calls
    • onAccessibilityChild

      protected void onAccessibilityChild(Widget<?> child, Accessibility a)
      Description copied from class: Widget
      Adds what only this widget knows about one of its children: the role a mounted list cell takes, its position in the data, its selected state.

      Called on the UI thread with the child's node current and this widget's locale in scope, after the child has described itself, so what is written here wins. The child's identity is not decided here but in Widget.onAccessibilityChildIdentity(limn.scene.Widget<?>, limn.accessibility.Accessibility), which runs first; Accessibility.key(long) refuses to be called from here.

      This is the only place Accessibility.delegate(limn.accessibility.Accessible.Action) may be called: a verb this widget claims on the child — a list's SELECT on a row — is published on the child's node and routed to Widget.onAccessibilityChildAction(limn.scene.Widget<?>, long, limn.accessibility.Accessible.Action, limn.accessibility.Accessible.Argument). The builder refuses a delegation from anywhere else, and refuses one here for a verb the child declared for itself, for a verb that takes an argument, and for FOCUS or SCROLL_INTO_VIEW on a focusable child, which the walk performs on its own. A verb written here with Accessibility.action(limn.accessibility.Accessible.Action) would be dispatched to the child's own Widget.onAccessibilityAction(limn.accessibility.Accessible.Action, limn.accessibility.Accessible.Argument), which is why a child hook writes facts and delegates verbs.

      Overrides:
      onAccessibilityChild in class Widget<Tree<T>>
      Parameters:
      child - the child being described
      a - the child's node
    • onAccessibilityChildAction

      protected boolean onAccessibilityChildAction(Widget<?> child, long key, Accessible.Action action, Accessible.Argument arg)
      A verb the tree claimed on a row's cell, performed by the rules of the ROWS shape (RowsAccessibility.performOnRow(limn.components.internal.a11y.RowsAccessibility.Host<R>, R, limn.accessibility.Accessible.Action)) over Tree<T>.limn.components.tree.Tree.RowsHost, the tree's own mechanisms: each through the seam the equivalent gesture takes at USER. A verb the row did not publish is refused, which the platform never learns of (the published list is the only refusal it sees).
      Overrides:
      onAccessibilityChildAction in class Widget<Tree<T>>
      Parameters:
      child - the child the verb was addressed to
      key - the identity key this widget gave that child, or 0 when it gave none
      action - what was asked
      arg - the argument, or Accessible.Argument.NONE
      Returns:
      whether this widget did it
    • onAccessibilityAction

      protected boolean onAccessibilityAction(Accessible.Action action, Accessible.Argument arg)
      The tree's one verb of its own: PRESS activates the cursor row.
      Overrides:
      onAccessibilityAction in class Widget<Tree<T>>
      Parameters:
      action - what was asked; one of the seventeen, including the four that carry an argument
      arg - the argument, or Accessible.Argument.NONE
      Returns:
      whether this widget did it. false is the honest answer for a verb it does not offer.