Class Table<T>

java.lang.Object
limn.scene.Widget<Table<T>>
limn.components.table.Table<T>
Type Parameters:
T - the row type
All Implemented Interfaces:
Scrollable

public final class Table<T> extends Widget<Table<T>> implements Scrollable
A table: Columns over a list the application owns, virtualized on both axes.

The rows are a List<T> held by reference and re-read on refresh(); nothing is copied and nothing is read until it is on screen. Vertically the table keeps the anchor-and-walk ListView keeps — which row sits at which y, a walk from there until the viewport is full, a mean height as the scroll estimate — over row slots rather than widgets: a value cell is its formatted text, shaped once and held, and only a Column.widget cell is a real child, mounted and recycled with its row. Horizontally the columns are a band: widths are resolved once per layout, and only the columns that intersect the viewport are shaped and painted. The header is pinned.

Selection is by row and by model index, in one of three modes, and it survives a sort because a sort is a permutation over the application's list and never a reordering of it. A row is its record: across a refresh() the selection follows the records it named, wherever the list holds them now, by equals or by a key. Separately, the arrow keys move a focus cell, which is what a screen reader's cursor stands on. Enter and a double click activate the cursor row — the focus cell's row, which is the lead in SINGLE and may differ from it in MULTI.

Cells are not edited in place, and will not be. In-place editing is a spreadsheet's interaction and reads as one everywhere else: a field that appears where a value was, a save on a keystroke the user did not mean as one, an error with nowhere to stand. A table is for reading, comparing, sorting and choosing; editing a record wants the record whole. Open a Dialog or a panel with the record as a form from onActivate, keep a form beside the table following its selection through Widget.observeChanges(limn.scene.ChangeObserver) for master-and-detail, put a switch or a button in a Column.widget cell for the one-gesture cases, or act on the whole selection at once; then change the list and refresh().

Sorting: a click on a sortable header cycles ascending, descending and the model's order. The table sorts by default; onSortRequest hands the click to the application instead, for rows a server orders.

The footer is a summary row pinned under the rows, as the header is pinned over them, and it appears as soon as one column has something for it (Column.footer, Column.footerSum() and the other aggregates). It is computed on setRows(java.util.List<T>) and refresh() and never per frame.

Inside a scroller the table scrolls itself first and hands the wheel on at either end: a detent that moves neither offset — the rows already at the top or the bottom, the columns at either edge, or a table that fits — is left unconsumed and reaches the scroll pane that holds it. Under an unbounded height the table prefers setVisibleRows(int) rows of the step's seed height, plus its header and footer.

  • Constructor Details

  • Method Details

    • of

      @SafeVarargs public static <T> Table<T> of(Column<T>... columns)
      A table over columns.
      Type Parameters:
      T - the row type
      Parameters:
      columns - the columns, in reading order; at least one
      Returns:
      the table
    • setRows

      public Table<T> setRows(List<T> rows)
      Hands the table the application's rows, held by reference and never copied. The selection is dropped, the sort is re-applied, and the view starts at the top. A row a popup hangs from by one of its widget cells whose record the new rows hold is kept, as refresh() keeps it: its texts show the record found again, and its widget cells stay those built for the record as it was handed then, the popup open and the keyboard where it was. After new objects equal to the old, the record the table holds is the new object: a cell made by Column.widget(I18nString, java.util.function.BiFunction) reads it from its supplier, so a choice made in a combo box whose list stayed open through the push lands there, while a cell made by Column.widget(I18nString, Function) still holds the old object, and a menu on it that can outlive a push acts through the record's key. A row whose field has the keyboard, or has it due back as its own menu closes, is not kept: the field would stay bound to the old object and an edit made in it would go there, so it goes, its menu with it, and the keyboard falls back to the table. UI thread only.
      Parameters:
      rows - the rows; the table reads them on demand, so a change to the list's contents is invisible until refresh()
      Returns:
      this table
    • rows

      public List<T> rows()
      Returns:
      the rows, as handed to setRows(java.util.List<T>)
    • rowCount

      public int rowCount()
      Returns:
      how many rows the model holds
    • columns

      public List<Column<T>> columns()
      Returns:
      the columns, in reading order as they stand now; hidden ones included
    • moveColumn

      public Table<T> moveColumn(Column<T> column, int index)
      Moves a column to another place among columns(). The rows are laid out again in the new order, and the cell the keyboard is on stays with its column. A caller's write: onColumnsChange is not told.
      Parameters:
      column - one of this table's columns
      index - the place it takes among columns(), from 0
      Returns:
      this table
      Throws:
      IllegalArgumentException - if the column is not this table's
      IndexOutOfBoundsException - if there is no such place
    • setColumnReorderingAllowed

      public Table<T> setColumnReorderingAllowed(boolean allowed)
      Sets whether the user may move a column by dragging its header or with Alt+Shift+Left and Right (default on, as resizing is).
      Parameters:
      allowed - whether columns can be moved by the user
      Returns:
      this table
    • isColumnReorderingAllowed

      public boolean isColumnReorderingAllowed()
      Returns:
      whether the user may move columns
    • setFrozenColumns

      public Table<T> setFrozenColumns(int count)
      Keeps the first shown columns in place while the others scroll under them: the name beside a year of monthly figures. The user moves a column only within its group, the kept ones or the others. Where the kept columns would leave no room for the rest, none is kept.
      Parameters:
      count - how many leading shown columns stay; 0 for none
      Returns:
      this table
      Throws:
      IllegalArgumentException - if count is negative
    • frozenColumns

      public int frozenColumns()
      Returns:
      how many leading shown columns are asked to stay in place
    • onColumnsChange

      public Table<T> onColumnsChange(@Nullable Runnable handler)
      The application's response to the user rearranging the columns: one moved, by a drag of its header or by the keyboard, or one resized. It is when an application saves columns()' order and widths. Never for moveColumn(limn.components.table.Column<T>, int) or a width set in code.
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this table
      Throws:
      IllegalStateException - if a handler is already registered
    • footerTextOf

      public @Nullable String footerTextOf(Column<T> column)
      Parameters:
      column - one of this table's columns
      Returns:
      the text its footer cell shows, as computed by the last setRows(java.util.List<T>) or refresh(); null when the column puts nothing in the footer
    • widthOf

      public float widthOf(Column<T> column)
      Parameters:
      column - one of this table's columns
      Returns:
      the width the last layout gave it, weight included; 0 while it is hidden or before the first layout
    • refresh

      public void refresh()
      Re-reads the rows and re-lays out: call after the list's contents change. A column's setters need no call: the table takes a change to its width, visibility, alignment, footer or sorting by itself. The sort is re-applied and the scroll position is kept, clamped.

      A row is its record: the selection, the lead, the focus cell and the range anchor follow their records to wherever the list holds them now, after an insert, a remove, a reorder or the application's own sort (onSortRequest). A record is found again by its key — the record itself, by equals, unless one is set — and records with equal keys are told apart by occurrence: the third equal record stays the third. Finding them is one read of the rows, stopping at the last one found; a table with nothing selected and no focus cell reads nothing. A selected record the list no longer holds leaves the selection, announced as SELECTION/ADJUSTMENT; a vanished lead makes the last selected row the lead; a vanished focus row or anchor keeps its position, clamped. A focus row that moved is announced as ACTIVE/ADJUSTMENT; when the refresh answers a sort request it is also revealed with the least scroll, as the table's own sort does, and otherwise the scroll position is kept. Then CHILDREN/ CODE; nothing reaches a handler. A row's accessible node follows its record the same way: the rows the last publish described keep their nodes wherever their records went, across as many refreshes as come before the next frame, and a reader's verb sent before them acts on the record it named, or is refused when that record is gone. A row published earlier and scrolled away since loses its node on the next refresh: the node names that record or nothing, never another.

      A row from one of whose widget cells a popup hangs (a context menu opened on it, a popover) follows its record as the row of a focused widget cell does: its widgets stay, so the popup stays open through a refresh a timer or a server push makes, acting on the record its widget cell was built for; when the record is gone they are let go of and the popup closes with them. Where the list now holds another object for that record, a cell made by Column.widget(I18nString, java.util.function.BiFunction) reads that object from its supplier, as after setRows(java.util.List<T>). UI thread only.

    • rowKey

      public Table<T> rowKey(@Nullable Function<? super T,?> key)
      Names what makes a row the record it is, for refresh() to follow the selection, the lead, the focus cell and the anchor across a change to the list: Order::id for rows that are records with an identity, or nothing, in which case the record itself is the key and equal records are told apart by occurrence. A key is taken when a row enters one of the four and compared by equals; keys are expected to be unique, and when two rows share one the first found wins. Setting it re-reads the keys of every row the table is following. UI thread only.
      Parameters:
      key - the function from a row to its key, or null for the record itself
      Returns:
      this table
    • setSelectionMode

      public Table<T> setSelectionMode(SelectionMode mode)
      Sets how many rows may be selected (default SelectionMode.SINGLE). Narrowing the mode trims the selection to the lead row, or to nothing. UI thread only.
      Parameters:
      mode - the mode; never null
      Returns:
      this table
    • selectionMode

      public SelectionMode selectionMode()
      Returns:
      how many rows may be selected
    • setSelectedRow

      public Table<T> setSelectedRow(int modelIndex)
      Selects one row alone, moves the focus cell to it and scrolls it into view: a caller's write, so it announces SELECTION/CODE and reaches no handler. Selecting the row that is already the only selected one changes nothing and announces nothing. UI thread only.
      Parameters:
      modelIndex - a row in [0, rowCount())
      Returns:
      this table
      Throws:
      IndexOutOfBoundsException - if the index is outside that range
      IllegalStateException - if the mode is SelectionMode.NONE
    • setSelectedRows

      public Table<T> setSelectedRows(int... modelIndices)
      Replaces the selection with exactly these rows, the last one the lead, and scrolls the lead into view: what an application restoring a saved selection calls. In SelectionMode.SINGLE only one row may be named. Announces SELECTION/ CODE once when the set changed. UI thread only.
      Parameters:
      modelIndices - rows in [0, rowCount()); none clears the selection
      Returns:
      this table
      Throws:
      IndexOutOfBoundsException - if an index is outside that range
      IllegalStateException - if the mode is SelectionMode.NONE, or SINGLE and more than one row is named
    • clearSelection

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

      public Table<T> selectAll()
      Selects every row, in SelectionMode.MULTI; the lead row is kept, or becomes the first row. A caller's write, announced as SELECTION/CODE; Ctrl+A enters the same seam as the user's. UI thread only.
      Returns:
      this table
    • selectedRow

      public int selectedRow()
      Returns:
      the lead row's model index, or -1 when nothing is selected
    • selectedRows

      public int[] selectedRows()
      Returns:
      the selected rows' model indices, ascending; empty when nothing is selected
    • isSelected

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

      public Table<T> onSelect(@Nullable Runnable handler)
      The application's response to the user changing the selection: a click, a key, an assistive technology's select. Never for setSelectedRow(int), setSelectedRows(int...), clearSelection(), selectAll() or a setRows(java.util.List<T>), refresh() or setSelectionMode(limn.components.SelectionMode) that moved it, which are the caller's or the table's own; to hear every change whatever caused it, watch the table.
      Parameters:
      handler - the handler, or null to clear the slot
      Returns:
      this table
      Throws:
      IllegalStateException - if a handler is already registered
    • onActivate

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

      public void activate()
      Announces that the cursor row — the focus cell's row — was opened, as INVOKED/ CODE: a caller's verb, which reaches a watcher and not onActivate, the way Enter does. Nothing before the keyboard has been in the table. UI thread only.
    • focusRow

      public int focusRow()
      Returns:
      the focus cell's row as shown, or -1 before the keyboard was in the table
    • focusColumn

      public int focusColumn()
      Returns:
      the focus cell's column, as an index among the shown columns; when the column it stood on is hidden the cell moves to the nearest shown column, and when a column before it is hidden the index shifts and the cell stays on its column
    • isHeaderFocused

      public boolean isHeaderFocused()
      Returns:
      whether the keyboard, while in this table, is on the header rather than in the rows: Tab enters at the header when a shown column can be sorted, Left and Right move its column cursor and Space sorts the column under it
    • headerColumn

      public int headerColumn()
      Returns:
      the header's column cursor, as an index among the shown columns
    • setSort

      public Table<T> setSort(Column<T> column, SortOrder order)
      Orders the rows on a column, or restores the model's order with SortOrder.NONE. The application's list is untouched: the table keeps a permutation, and the selection, which is by model row, is unchanged. When onSortRequest is set this only records the order shown in the header; the application is expected to have ordered the list. UI thread only.
      Parameters:
      column - the column, one of this table's; ignored when the order is none
      order - the order; never null
      Returns:
      this table
      Throws:
      IllegalArgumentException - if the column is not this table's or not sortable
    • sortColumn

      public @Nullable Column<T> sortColumn()
      Returns:
      the column the rows are ordered on, or null in the model's order
    • sortOrder

      public SortOrder sortOrder()
      Returns:
      how the rows are ordered
    • onSortRequest

      public Table<T> onSortRequest(@Nullable BiConsumer<Column<T>,SortOrder> handler)
      Hands header clicks to the application instead of sorting: the handler is told the column and the order the click asks for, orders the list itself and calls refresh(), which carries the selection and the focus cell to where their records are now. The header shows the order the click asked for from the click itself. A handler, so it answers the user's click and never setSort(limn.components.table.Column<T>, limn.components.table.SortOrder); one slot, as every onX is: null clears it, a second handler over the first throws.

      The slot changing hands re-sorts at once when the header shows an order: setting a handler drops the table's permutation, so the rows show in the application's order (the application is expected to have ordered them, or to order them and refresh()); clearing it restores the table's own sort on the column the header shows. Both carry the focus cell with its record and are announced as a sort is, CHILDREN/CODE, reaching no handler. UI thread only.

      Parameters:
      handler - what to tell, or null to clear the slot
      Returns:
      this table
      Throws:
      IllegalStateException - if a handler is already registered
    • viewToModel

      public int viewToModel(int viewIndex)
      Parameters:
      viewIndex - a row as shown, in [0, rowCount())
      Returns:
      the model row shown there
    • modelToView

      public int modelToView(int modelIndex)
      Parameters:
      modelIndex - a model row, in [0, rowCount())
      Returns:
      where it is shown
    • firstVisibleRow

      public int firstVisibleRow()
      Returns:
      the first row shown, as a view index; for tests and inspection
    • setStriped

      public Table<T> setStriped(boolean striped)
      Sets whether every second row is tinted (default on).
      Parameters:
      striped - whether rows alternate
      Returns:
      this table
    • isStriped

      public boolean isStriped()
      Returns:
      whether every second row is tinted
    • setShowHeader

      public Table<T> setShowHeader(boolean show)
      Sets whether the header row is shown (default on). Without it nothing sorts or resizes by pointer.
      Parameters:
      show - whether the header is shown
      Returns:
      this table
    • isShowHeader

      public boolean isShowHeader()
      Returns:
      whether the header row is shown
    • setScrollbarPolicy

      public Table<T> setScrollbarPolicy(ScrollBar.Policy policy)
      Sets when the scroll bars are shown (default ScrollBar.Policy.AUTO).
      Parameters:
      policy - the policy
      Returns:
      this table
    • scrollbarPolicy

      public ScrollBar.Policy scrollbarPolicy()
      Returns:
      when the scroll bars are shown
    • setBarLayout

      public Table<T> setBarLayout(ScrollGutters.Layout layout)
      Sets whether the bars float over the rows or reserve strips of their own (default ScrollGutters.Layout.OVERLAY).
      Parameters:
      layout - the layout
      Returns:
      this table
    • barLayout

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

      public Table<T> setVisibleRows(int rows)
      Sets how many rows tall the rows' viewport prefers to be when the parent gives the table no height — a table inside a ScrollView or an unconstrained column — as a count of the step's seed rows (default 8); the header and the footer add their own strips. 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 table
      Throws:
      IllegalArgumentException - if rows is below one
    • visibleRows

      public int visibleRows()
      How many seed rows tall the rows' viewport prefers to be under an unbounded height.
    • 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 last column. UI thread only.
      Parameters:
      dx - how far along the columns
      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
    • 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<Table<T>>
    • setEmptyContent

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

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

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

      public Table<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 table
    • clearEmptyMessage

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

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

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

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

      public @Nullable Cursor cursor()
      Overrides:
      cursor in class Widget<Table<T>>
      Returns:
      the mouse cursor this widget requests while hovered, or null to inherit from an ancestor (the scene falls back to Cursor.DEFAULT at the root). Non-final so a component can override it with a state-dependent shape.
    • onMouseEvent

      protected void onMouseEvent(MouseEvent event)
      Description copied from class: Widget
      Mouse events (bubbling); call event.consume() when handled.
      Overrides:
      onMouseEvent in class Widget<Table<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<Table<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<Table<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<Table<T>>
    • 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<Table<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<Table<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<Table<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<Table<T>>
      Parameters:
      event - what was dropped and where
    • onFocusGained

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

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

      protected float clipX(Widget<?> child)
      Description copied from class: Widget
      The left edge, in this widget's own coordinates, of the rectangle Widget.clipsChildren() clips child to.

      The whole box by default. A widget that paints one child inside an inset -- a scroll pane whose reserved gutters hold its bars, clipping the content to the viewport and the bars to the box -- answers per child, so that Widget.isShowing() and the damage clamp agree with what it paints: until it did, a descendant lying wholly inside the gutter was published on screen in the very rectangle the bar occupies. Consulted only while clipsChildren() is true, and it must not allocate, because the showing test runs for every node of every accessible walk.

      Overrides:
      clipX in class Widget<Table<T>>
      Parameters:
      child - the direct child the clipped descendant is under
      Returns:
      the clip rectangle's left edge
    • clipY

      protected float clipY(Widget<?> child)
      Description copied from class: Widget
      The top edge of the rectangle this widget clips child to; see Widget.clipX(Widget).
      Overrides:
      clipY in class Widget<Table<T>>
      Parameters:
      child - the direct child the clipped descendant is under
      Returns:
      the clip rectangle's top edge
    • clipWidth

      protected float clipWidth(Widget<?> child)
      Description copied from class: Widget
      The width of the rectangle this widget clips child to; see Widget.clipX(Widget).
      Overrides:
      clipWidth in class Widget<Table<T>>
      Parameters:
      child - the direct child the clipped descendant is under
      Returns:
      the clip rectangle's width
    • clipHeight

      protected float clipHeight(Widget<?> child)
      Description copied from class: Widget
      The height of the rectangle this widget clips child to; see Widget.clipX(Widget).
      Overrides:
      clipHeight in class Widget<Table<T>>
      Parameters:
      child - the direct child the clipped descendant is under
      Returns:
      the clip rectangle's height
    • onAccessibility

      protected void onAccessibility(Accessibility a)
      One table node: its shape, its selection and its scroll, then a header group with a header cell per shown column, then one row per realized data row with a cell per shown column. Nothing is formatted here: every name is a string a slot already holds, handed over with the row's witness, or the column's own I18nString.
      Overrides:
      onAccessibility in class Widget<Table<T>>
      Parameters:
      a - the node being described
    • onAccessibilityChildIdentity

      protected void onAccessibilityChildIdentity(Widget<?> child, Accessibility a)
      A widget cell hangs under the synthetic ROW of the record it shows and is keyed by its column within that row: a reader walking the row finds the control among its cells, in column order, and the cell's identity follows the record the row is keyed by, so a control recycled to another row carries nothing of the old one. The key is the column alone, because the row's node scopes it; no packing of row and column into one number.
      Overrides:
      onAccessibilityChildIdentity in class Widget<Table<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<Table<T>>
      Parameters:
      child - the child being described
      a - the child's node
    • onAccessibilityAction

      protected boolean onAccessibilityAction(Accessible.Action action, Accessible.Argument arg)
      Description copied from class: Widget
      Performs an action an assistive technology asked of this widget, as the user.

      Called on the UI thread from a posted task, after the scene has re-checked that this widget is still attached, still enabled with every ancestor, still showing and still reachable. The widget performs its own action: it reaches its own private from-the-user path with its own guards, so an assistive technology's toggle notifies the application exactly as a click does and no component has to grow a public method that re-derives a guard it already has.

      The platform is not told the answer. It was answered from the published snapshot before this ran: the bridge accepts a verb the node publishes and refuses one it does not, synchronously, and the post that reaches here carries no reply back. So false here is honest bookkeeping and nothing more — a refused PRESS raises no INVOKED — and the one contract a reader can see is the published one: a node accepts exactly the parameterless verbs in its action facet plus the setters its writable facets imply. A widget that would answer true for a verb it did not publish is a defect the gallery ratchet in limn-demo reports; a widget that accepts a synonym publishes it.

      Overrides:
      onAccessibilityAction in class Widget<Table<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.
    • onSyntheticAction

      protected boolean onSyntheticAction(long key, Accessible.Action action, Accessible.Argument arg)
      A reader's verb on a row or a cell, decoded from the key the node was published with: a row's key is its record's row identity, a cell's carries that and its column (until 2026-09-14 a cell was keyed by its column alone and a select on it selected the row of that number). The verbs are the published ones and no other — a cell accepts FOCUS alone — and each goes through the seam the matching gesture takes at USER: SELECT is the click, ADD_TO_SELECTION and DESELECT the command-click's toggle without its cursor move (only SELECT and FOCUS move the cursor), FOCUS a cursor move that selects nothing. A row is named by the identity of the record the snapshot published it for: a verb sent before a refresh() that inserted a row above acts on that same record where it stands now, and one whose record left the list is refused. Until 2026-09-15 it acted on whatever record stood at the published model index.
      Overrides:
      onSyntheticAction in class Widget<Table<T>>
      Parameters:
      key - the key this widget gave that child when it declared it
      action - what was asked
      arg - the argument, or Accessible.Argument.NONE
      Returns:
      whether this widget did it