Class ListView<T>

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

public final class ListView<T> extends Widget<ListView<T>> implements Scrollable
A vertically scrolling list that virtualizes its rows like a JTable/RecyclerView: only the rows in the viewport get a widget, so a list of a million rows costs the same as one of twenty (layout and paint are O(visible)).

Rows may have different heights: e.g. small grouping headers among normal cards. There is no global height cache and no binary search: the list keeps an anchor (which row sits where) and walks a handful of rows from it each frame, measuring each on demand. Scroll-thumb size/position are estimated from the average measured height (good enough for a scroll indicator).

The items are yours: a List<T> the application owns, handed over with setItems(java.util.List<T>) and re-read after a change with refresh(), as a Table's rows are. A row's widget comes from a cell function, made fresh for each row that comes into view (ListView(Function)), from a pool that binds a recycled widget to its item (pooled(java.util.function.Supplier<? extends W>, java.util.function.BiConsumer<? super W, ? super T>)), or from a cell function the application pools behind itself, handed each widget back as it scrolls out (ListView(Function, Consumer)). The list owns only the tree and the positioning.

Selection is a SelectionMode: SINGLE by default, NONE, or MULTI with the gestures Table and Tree share. The keyboard cursor is not the selection in NONE and MULTI; in SINGLE they are one.

Interaction: the wheel scrolls (with the shared ScrollBar); the list is focusable and, while focused, Up/Down/Home/End/PageUp/PageDown move a highlighted selection (auto-scrolling to reveal it) and Enter activates it; clicking a row selects it (clicks on a row's own buttons reach those buttons).

Widget.children() is the bar and then the realized rows in data order, whichever direction the scroll realized them in — which is what makes a Tab through those row buttons, and the reading order an assistive technology is given, agree with the list on screen.

The row holding the keyboard focus is realized for as long as it holds it, wherever a scroll has taken the viewport: it stays mounted in data order, laid out outside the viewport, unpainted and unreachable by the pointer, and published to an assistive technology as the list item it is, not showing. A screen reader whose cursor follows the focus onto a row is otherwise left standing on a node a page scroll deleted. Every other row outside the viewport goes back back to its cell source, and so does that one the moment the focus leaves it or the data is refreshed. And so is the selected row while the list itself holds the keyboard: the selection is the reader's cursor here, and a wheel or a bar drag that scrolled it away used to recycle it, leaving the reader's cursor on nothing until the next arrow key. It is kept the same way — mounted, outside the viewport, published not showing and still the cursor — across a refresh too, and released by the first pass after the keyboard leaves.

Rows can be dragged, once setDragEnabled(boolean) says so: the selected rows, to another place in this list (onReorder), to another widget or to another program. And things can be dropped between its rows (onDrop(DropHandler)). The items stay the application's: the list never rearranges them, it asks, and shows what it is handed back.

Size steps propagate rather than being imposed. Rows are application-supplied widgets in this list's subtree, so they resolve the ControlSize themselves and list.setControlSize(SMALL) shortens them because they re-measure. Only three metrics are the list's own: the row-height seed, used for every scroll estimate before anything has been measured and for the intrinsic height under an unbounded constraint always (setVisibleRows(int) rows of it), the intrinsic width under an unbounded constraint, and the selection ring's corner radius.

Inside a scroller the list scrolls itself first and hands the wheel on at either end: a detent that finds this list already at the top or the bottom is left unconsumed and reaches the scroll pane that holds it.

The scroll bar does not take part in the size axis (ScrollBar.thickness() is 15 pt at every step), and it overlays the rows rather than insetting them, so at a compact step it covers a larger fraction of a shorter row. An accepted cost of one scrollbar geometry process-wide.

  • Field Details

  • Constructor Details

    • ListView

      public ListView(Function<? super T,? extends Widget<?>> cellFor)
      A list whose rows are made by cellFor, a fresh widget each time a row comes into view; nothing is recycled. The items start empty: see setItems(java.util.List<T>).
      Parameters:
      cellFor - the widget for an item, populated and ready to show; never null
    • ListView

      public ListView(Function<? super T,? extends Widget<?>> cellFor, Consumer<? super Widget<?>> recycle)
      A list whose rows are made by cellFor and handed back to recycle when they scroll out, so the application can keep them and hand them out again: the form for rows of several kinds, each pooled on its own, which pooled(java.util.function.Supplier<? extends W>, java.util.function.BiConsumer<? super W, ? super T>) does not cover.
      Parameters:
      cellFor - the widget for an item, populated and ready to show; never null. It may be one recycle was given earlier.
      recycle - receives a row widget that scrolled out
  • Method Details

    • pooled

      public static <T, W extends Widget> ListView<T> pooled(Supplier<? extends W> create, BiConsumer<? super W,? super T> bind)
      A list whose row widgets are pooled: a widget that scrolls out is kept, and bound to the next item that comes into view instead of a new one being made. What a long list of the same kind of row wants.
      Type Parameters:
      T - the item type
      W - the row widget type
      Parameters:
      create - makes a row widget when the pool is empty
      bind - fills a row widget with an item, a recycled one included
      Returns:
      the list, with no items yet
    • setBarLayout

      public ListView<T> setBarLayout(ScrollGutters.Layout layout)
      Sets whether the bar floats over the rows or reserves a strip of its own (default ScrollGutters.Layout.OVERLAY). Reserved is what a list of records usually wants: the trailing edge of a row is where a count, a date or a status chip goes, and a thumb over it is a defect. Trailing and not right: the bar and the row's last column are both on the left of a list that reads right to left, and they move there together.
    • barLayout

      public ScrollGutters.Layout barLayout()
      Whether the scrollbar overlays the rows or reserves a gutter.
    • setScrollbarPolicy

      public ListView<T> setScrollbarPolicy(ScrollBar.Policy policy)
      Sets when the vertical scrollbar is shown (default ScrollBar.Policy.AUTO).
    • setVisibleRows

      public ListView<T> setVisibleRows(int rows)
      Sets how many rows tall this list prefers to be when its parent gives it no height — a list inside a ScrollView or an unconstrained column — as a count of the step's seed rows (default 6). A bounded height from the parent always wins; this is the free-axis fallback only. The preference is the seed's and not the realized rows' on purpose: a preference that followed the measured average moved every time a row of another height scrolled in, and re-laid out the parent with it. UI thread only.
      Parameters:
      rows - a row count of at least one
      Returns:
      this list
      Throws:
      IllegalArgumentException - if rows is below one
    • visibleRows

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

      public ListView<T> onSelect(@Nullable Runnable handler)
      The application's response to the user changing the selection: a click, a key, or an assistive technology's select. Never for setSelectedIndex(int), clearSelection(), selectAll(), setItems(java.util.List<T>) or a refresh() that collapsed the selection, which are the caller's or the list's own; to hear every change whatever caused it, watch the list instead. The handler reads what it needs: selectedIndex(), selectedIndices(), selectedItem().
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this list
      Throws:
      IllegalStateException - if a handler is already registered
    • onActivate

      public ListView<T> onActivate(@Nullable IntConsumer handler)
      The application's response to the user opening the cursor row: Enter, a double click, or an assistive technology's press. Handed the row's index, as Table's is. Never for activate(), which is a caller's verb.
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this list
      Throws:
      IllegalStateException - if a handler is already registered
    • 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<ListView<T>>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • setDragEnabled

      public ListView<T> setDragEnabled(boolean enabled)
      Lets the user drag rows out of this list (default off): the selected rows when the row pressed is one of them, and that row alone otherwise. From the keyboard, the scene's drag key picks up the selection. What they carry is ITEMS 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 (a button, a field) keep their gestures. UI thread only.
      Parameters:
      enabled - whether rows can be dragged
      Returns:
      this list
    • isDragEnabled

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

      public ListView<T> setDragContent(@Nullable Function<? super List<T>,Transfer> content)
      Says what dragged rows carry, in place of ITEMS and their text: the application's own format, a file, a picture. UI thread only.
      Parameters:
      content - makes the transfer for the rows dragged, or null for the default
      Returns:
      this list
    • onReorder

      public ListView<T> onReorder(@Nullable ListView.Reorder<T> handler)
      Lets the user move rows within this list: dragged, when dragging is on, and with Alt+Shift+Up and Alt+Shift+Down, which move the selected rows one place. The handler moves the items; the list then shows them, keeps them selected and says where they went. UI thread only.
      Parameters:
      handler - moves the items, or null for a list that does not reorder
      Returns:
      this list
    • onMoveOut

      public ListView<T> onMoveOut(@Nullable Consumer<? super List<T>> handler)
      Hears that rows dragged out of this list 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 items, or null
      Returns:
      this list
    • onDrop

      public ListView<T> onDrop(@Nullable ListView.DropHandler handler)
      Lets things be dropped between the rows of this list: what carries one of the formats named, from another list, another widget or another program. A line shows where it would land. UI thread only.
      Parameters:
      handler - takes a drop, or null for a list that takes none
      Returns:
      this list
    • setDropFormats

      public ListView<T> setDropFormats(DataFormat<?>... formats)
      Names what onDrop(DropHandler) takes: a drag carrying any of these is accepted. ITEMS takes another list's rows, DataFormat.FILES files. UI thread only.
      Parameters:
      formats - the formats taken; none takes nothing
      Returns:
      this list
    • setDropAction

      public ListView<T> setDropAction(@Nullable DropAction action)
      Says what a drop from elsewhere does here, whatever the source would rather: a list 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. Rows moved within this list are always a move. UI thread only.
      Parameters:
      action - the action asked for, or null for the one proposed
      Returns:
      this list
    • dropAction

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

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

      public boolean isDragWithinWindow()
      Returns:
      whether a drag of this list's rows 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<ListView<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<ListView<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<ListView<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<ListView<T>>
      Parameters:
      event - what was dropped and where
    • setEmptyContent

      public ListView<T> setEmptyContent(@Nullable Widget<?> content)
      Shows content in the rows' viewport whenever the list has no rows (a list with no items), and takes it away when one arrives: the list 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 list's last child while it shows, so its own controls (a "Clear filter" button) follow the list in the Tab order and a reader finds them after its (no) rows; they belong to no selection. It is this list'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 list
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this list
    • setEmptyContent

      public ListView<T> setEmptyContent(@Nullable Widget<?> content, @Nullable Label message)
      Shows content while there are no rows, as setEmptyContent(Widget) does, and describes the list by message while it shows, so a reader landing on it hears "list, 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 list
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this list
    • setEmptyMessage

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

      public ListView<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 list
    • clearEmptyMessage

      public ListView<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 list
    • 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 ListView<T> setLoading(boolean loading)
      Says the list 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 list is published busy, and a reader is told both ends: "Loading Orders" at the start, with the name a reader hears the list 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 list 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 list
    • isLoading

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

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

      public ListView<T> setLoadingContent(@Nullable Widget<?> content, @Nullable Label message)
      Replaces what shows while the list loads with no rows, and describes the list 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 list
      Throws:
      IllegalArgumentException - when content is already in another container (another widget showing it included), or holds this list
    • isShowingLoadingContent

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

      protected void onDetached()
      Leaving the window, the empty or loading content shown goes back to being nobody's child.
      Overrides:
      onDetached in class Widget<ListView<T>>
    • setItems

      public ListView<T> setItems(List<T> newItems)
      Shows newItems, which the application keeps: the list reads it where it is and never copies or changes it. A new list is a new world: the selection and the cursor go (announced as SELECTION/ADJUSTMENT when there was one), the view returns to the top, and CHILDREN/CODE is announced. After changing the same list in place, call refresh() instead. A row a popup hangs from (its context menu, a popover) whose item the new list holds is kept, as refresh() keeps it, but not one the keyboard is in or returns to, such as a field whose own menu is open. The popup acts on the item its row was built for, as the cell factory was given it: when the new list holds new objects equal to the old, that is the old object and not the list's, so a menu that can outlive a push acts through the item's key, or finds the item in items(), rather than changing the object it captured. UI thread only.
      Parameters:
      newItems - the items, in the order shown
      Returns:
      this list
    • items

      public List<T> items()
      Returns:
      the items, as handed to setItems(java.util.List<T>)
    • setItemName

      public ListView<T> setItemName(@Nullable Function<? super T,? extends I18nString> name)
      What to call a row for an assistive technology, when its own cell says nothing about itself — the common case, because a cell that paints its own text usually declares nothing, and the LIST_ITEM role the list writes onto it suppresses the warning that would have been the application's only notice. Asked for each realized row whose cell has no name, and for a selected row that is not realized, which is announced from the list's own node.

      Hand back a string the item holds. A name is carried over from the previous walk when it is the same object under the same locale and translation epoch, at no cost; one built inside this call is never the same object, so it is allocated and resolved again for every realized row on every damaged frame. A field of the item, or a constant, is what belongs here. UI thread only.

      Parameters:
      name - the row's name for an item, or null for none
      Returns:
      this list
    • setSelectionMode

      public ListView<T> setSelectionMode(SelectionMode mode)
      Sets how many rows the user may select (default SelectionMode.SINGLE). Narrowing keeps what the new mode can hold: NONE drops the selection and SINGLE keeps the lead, each announced as SELECTION/ADJUSTMENT. UI thread only.
      Parameters:
      mode - the mode
      Returns:
      this list
    • selectionMode

      public SelectionMode selectionMode()
      Returns:
      how many rows the user may select
    • selectedIndex

      public int selectedIndex()
      The lead of the selection: the row selected most recently that is still selected, or -1 when nothing is. In SINGLE it is the one selected row and also the cursor. A list is one of the widgets in this set that genuinely has no-selection as a state: clearSelection() reaches it, and a fresh list is in it.
    • selectedIndices

      public int[] selectedIndices()
      Returns:
      every selected row, ascending; empty when nothing is selected
    • isSelected

      public boolean isSelected(int index)
      Parameters:
      index - a row
      Returns:
      whether it is selected
    • selectedItem

      public @Nullable T selectedItem()
      Returns:
      the item at selectedIndex(), or null when nothing is selected
    • selectedItems

      public List<T> selectedItems()
      Returns:
      the selected items, in the order shown; empty when nothing is selected
    • cursorIndex

      public int cursorIndex()
      The row the keyboard is on, or -1: what the arrows move, what Enter opens, and what wears the focus ring while the list holds the keyboard. In SINGLE it is the selection; in MULTI a row toggled off keeps it; in NONE it moves with nothing selected.
    • rowCount

      public int rowCount()
      Row count: the size of the items.
    • firstVisibleIndex

      public int firstVisibleIndex()
      Returns:
      the first fully-or-partly visible row index (tests/inspection)
    • refresh

      public void refresh()
      Re-reads the items and re-lays out (call after changing the list in place). Announces CHILDREN/CODE after the rows are unmounted. Rows past the new end leave the selection; in SINGLE a selection past the end moves to the last row (or goes, when the list is now empty), and either is announced as SELECTION/ADJUSTMENT first: a watcher showing the selected record hears the list move by itself, with an origin that says so, where a handler -- the user's response -- is not run. A row is its index: an item that moved in the list leaves its selection where it was.

      A row a popup hangs from (a context menu opened on it, a popover) is not let go of while its item is still in the list, found again by equals at the occurrence nearest where it stood: it stays as it is, at its item's index now, so the popup stays open through a refresh a timer or a server push makes, and it is realized afresh, from the item as it is then, at the first layout after the popup closes. The popup acts on the item the row was built for, as the cell factory was given it, which is the list's own object unless the list now holds another equal to it (see setItems(java.util.List<T>)). When the item is gone, or the row would show another one, the row is let go of and the popup closes with it. So is a row the keyboard is in or returns to when its popup closes, such as a field whose own menu is open, as a refresh lets go of the row holding the keyboard, which falls back to the list: kept, the field would go on showing the item as it was, and stay bound to the object it was built for, which after a setItems(java.util.List<T>) of new objects is no longer the list's, so an edit made in it would go there. UI thread only.

    • setSelectedIndex

      public ListView<T> setSelectedIndex(int index)
      Selects one row, alone, and scrolls it into view: a caller's write, so it announces SELECTION/CODE and reaches no handler. The cursor goes there too. Selecting the row that is already the whole selection changes nothing, reveals nothing and announces nothing. In NONE it moves the cursor and selects nothing. UI thread only.
      Parameters:
      index - a row in [0, rowCount()). -1 is not an argument even though it is what selectedIndex() reports for an empty selection: clearSelection() is how that state is reached.
      Returns:
      this list
      Throws:
      IndexOutOfBoundsException - if index is outside that range, an empty list included, where every index is. An index that came from a search which found nothing, or from state saved against longer data, is a caller's bug here, exactly as it is for List.get.
    • clearSelection

      public ListView<T> clearSelection()
      Drops the selection: selectedIndex() becomes -1, announced as SELECTION/CODE. In SINGLE the cursor goes with it; in the other modes it stays where the keyboard left it. No-op when nothing is selected. UI thread only.
      Returns:
      this list
    • selectAll

      public ListView<T> selectAll()
      Selects every row, in SelectionMode.MULTI; nothing in the other modes. Announced as SELECTION/CODE when anything changed. UI thread only.
      Returns:
      this list
    • 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. An application that wants its own open-the-row code run calls that code. Nothing without a cursor. UI thread only.
    • scrollBy

      public void scrollBy(float dy)
      Scrolls by a delta in logical points (positive = toward the end). UI thread only.
    • revealRect

      public void revealRect(float x, float y, float rectWidth, float rectHeight)
      Scrolls the minimum so the rect (in viewport coordinates) becomes visible.
      Specified by:
      revealRect in interface Scrollable
    • 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<ListView<T>>
    • 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<ListView<T>>
    • 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<ListView<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<ListView<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<ListView<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
    • onMouseEvent

      protected void onMouseEvent(MouseEvent event)
      Description copied from class: Widget
      Mouse events (bubbling); call event.consume() when handled.
      Overrides:
      onMouseEvent in class Widget<ListView<T>>
    • 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<ListView<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<ListView<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<ListView<T>>
    • onAccessibility

      protected void onAccessibility(Accessibility a)
      One list node over the rows it has realized, carrying where the viewport sits and which row the cursor is on.

      Three facts decide the shape. The selection is not required: this class documents no-selection as a genuine resting state — a fresh list is in it, clearSelection() reaches it, and refresh() returns to it on an emptied list — unlike the combo, which refuses an empty item list, and unlike the tabbed pane, which is always selected while it has tabs. The active descendant is not declared here and cannot be: it is resolved in the copy from the first node in this subtree published Accessible.State.ACTIVE, which is the selected row. And the scroll facet is published unconditionally, as the scroll pane's and the tab strip's are, so that resizing past the fitting point moves two booleans rather than making a facet appear and disappear — but with no half-point slop, unlike the tab strip's: this widget's wheel gate and scrollBy(float)'s clamp both use the bare subtraction and will move a list that overflows by a third of a point, so a slop-gated boolean would advertise a refusal the widget does not make.

      The verb is on this node and not on a row: the walk records a published node's owner as the widget it came from, and the scene dispatches strictly to that owner, so a verb written onto a row would be sent to the application's own cell widget and refused there. It is offered only while something is selected, because activate() on an empty selection is a no-op and a verb that can only fail is worse than an absent one.

      Nothing is formatted here. The one string this hook can hand over is an I18nString the item holds, compared by reference, so a frame that damaged the list and moved nothing costs no memory at all.

      Overrides:
      onAccessibility in class Widget<ListView<T>>
      Parameters:
      a - the node being described
    • onAccessibilityChildIdentity

      protected void onAccessibilityChildIdentity(Widget<?> child, Accessibility a)
      A mounted cell's identity is its data index, answered before the cell describes itself so that its name is carried over under that identity and whatever the cell holds inside it follows the row when the cell is recycled.
      Overrides:
      onAccessibilityChildIdentity in class Widget<ListView<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)
      What only the list knows about a mounted row: which row it is, that it is one, where it sits in the data, and whether the cursor is on it.

      The identity key is the line the whole recycling story rests on, and it is answered by onAccessibilityChildIdentity(limn.scene.Widget<?>, limn.accessibility.Accessibility) before this hook runs. A cell is pooled and rebound, so the widget object is the wrong key the moment it is reused: keyed by the data index, a cell that carried row three and is recycled onto row nine is minted row nine's identifier, and a cell coming back to row three gets row three's identifier back out of the intern table. A screen reader holding row three therefore still holds row three.

      The role and the name are conditional and the reason is the same for both: a cell is an application's widget and may already have said what it is. A Row, a Column or a padded box says nothing and takes LIST_ITEM, which is also what keeps it from being deleted as scaffolding; a cell that is a button or a scroll pane keeps what it declared, because eliding a scroll pane would take its scroll facet with it. The name is the widening this step made to setItemName(java.util.function.Function<? super T, ? extends limn.i18n.I18nString>): rows commonly paint their own text and declare nothing, and the role written here suppresses the paints-and-says-nothing warning that would have been the application's only notice, so without asking for a name the ordinary case publishes thousands of nameless list items and nothing anywhere says so. An application's own setAccessibleName still wins, because it is applied after this.

      The position and the size of the set are the model's numbers and never the published tree's: a list that realizes twenty of five thousand rows still tells a reader which of five thousand it is on. And the selected row is marked active as well as selected, because here the selection is the cursor — there is no second highlight, unlike the combo's — and the keyboard stays on the list while it moves, which is exactly what an active descendant is for: without the bit a reader can enumerate the rows and never learn which one the user is on. Only while this list holds the keyboard: the cursor is resolved from the focused node down, so an unfocused list that marked its selected row active would hand the widget the user is actually in a cursor it does not have — a list nested inside another list's row cell would have handed the outer list its selected row as the outer list's own cursor, which is the cost the earlier text of this paragraph accepted and this gate removes.

      The verbs are delegated rather than written: a verb written onto a row would be dispatched to the application's own cell widget, whose hook answers false, and the verb would never be performed. A delegated verb is published on the row and routed to onAccessibilityChildAction(limn.scene.Widget<?>, long, limn.accessibility.Accessible.Action, limn.accessibility.Accessible.Argument), so a reader's "select this row" lands on the row it addressed and the list performs it. The row verb set, read against this widget: SELECT always, because a list has no selection mode and is never NONE; SCROLL_INTO_VIEW on a cell that cannot take the keyboard, because on one that can the walk already grants it free and a second performer is refused; never ADD_TO_SELECTION or DESELECT, because this list selects one row and has no multi-select to add to; and never FOCUS, because the selection is the cursor here and a focus that selected would be SELECT under another name. PRESS stays on the list — see onAccessibility(limn.accessibility.Accessibility).

      Overrides:
      onAccessibilityChild in class Widget<ListView<T>>
      Parameters:
      child - the child being described, which is the bar or one mounted cell
      a - the child's node
    • onAccessibilityChildAction

      protected boolean onAccessibilityChildAction(Widget<?> child, long key, Accessible.Action action, Accessible.Argument arg)
      A verb the list 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 ListView<T>.limn.components.ListView.RowsHost: SELECT makes that row the selection, as a click on it does, through the same USER seam and with the same reveal; SCROLL_INTO_VIEW reveals the row where it is, as the walk's free verb reveals a focusable one, and moves neither the selection nor the cursor.
      Overrides:
      onAccessibilityChildAction in class Widget<ListView<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)
      Opens the selected row, through the same USER seam Enter reaches -- and not through the public activate(), which is a caller's verb and reaches no handler.

      No enabled guard of its own: the node acted on here is this widget, so the scene's gate has already walked this list and every ancestor for isEnabled(), checked that it is showing, that the window is not modal-blocked and that it is inside the layer that owns input. The seam re-checks the selection for itself, so the application is told exactly as Enter tells it and nothing here is a second entry point.

      Overrides:
      onAccessibilityAction in class Widget<ListView<T>>
      Parameters:
      action - what was asked
      arg - unused; the one verb offered here is parameterless
      Returns:
      whether this list did it
    • onFocusGained

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

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