Class Table<T>
- Type Parameters:
T- the row type
- All Implemented Interfaces:
Scrollable
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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidactivate()Announces that the cursor row — the focus cell's row — was opened, asINVOKED/CODE: a caller's verb, which reaches a watcher and notonActivate, the way Enter does.Whether the scroll bars overlay the rows or reserve strips beside them.Takes the message shown with no rows away, assetEmptyMessage(null)would: that call does not compile, becausenullfits more than one of the overloads.Drops the selection, announced asSELECTION/CODE; nothing happens when nothing is selected.protected floatclipHeight(Widget<?> child) The height of the rectangle this widget clipschildto; seeWidget.clipX(Widget).protected booleanWhetherWidget.paintChildren(limn.graphics.Canvas)clips its children to this widget's bounds (scroll views, list viewports, tab strips).protected floatThe width of the rectangle this widget clipschildto; seeWidget.clipX(Widget).protected floatThe left edge, in this widget's own coordinates, of the rectangleWidget.clipsChildren()clipschildto.protected floatThe top edge of the rectangle this widget clipschildto; seeWidget.clipX(Widget).columns()@Nullable Cursorcursor()@Nullable Widget<?> intintintfocusRow()@Nullable StringfooterTextOf(Column<T> column) intprotected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.intbooleanbooleanbooleanbooleanisSelected(int modelIndex) booleanbooleanbooleanbooleanintmodelToView(int modelIndex) moveColumn(Column<T> column, int index) Moves a column to another place amongcolumns().static <T> Table<T> A table overcolumns.protected voidOne 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.protected booleanonAccessibilityAction(Accessible.Action action, Accessible.Argument arg) Performs an action an assistive technology asked of this widget, as the user.protected voidonAccessibilityChild(Widget<?> child, Accessibility a) 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.protected voidonAccessibilityChildIdentity(Widget<?> child, Accessibility a) A widget cell hangs under the syntheticROWof 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.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.protected booleanonCanEdit(EditCommand command) Whether this widget can do an editing command now: copy with something selected, paste with something to paste, undo with something to take back.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.protected voidLeaving the window, the empty or loading content shown goes back to being nobody's child.protected voidonDragExit(DragEvent event) The drag this widget was last asked about inWidget.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.protected voidonDragOver(DragEvent event) A drag is over this widget, or has moved on it.protected @Nullable DragonDragStart(DragStartEvent event) 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.protected voidWhat was dragged is dropped here: on the widget that accepted it last.protected booleanonEdit(EditCommand command) Does an editing command, as the user's own act.protected voidCalled when this widget takes keyboard focus.protected voidCalled when this widget loses keyboard focus.protected @Nullable Widget<?> onHitTest(float localX, float localY) What this widget answers for a point that reached it: one of its children's answers, itself, ornullto let the point through to what is beneath it.protected voidonKeyEvent(KeyEvent event) Key events (focused widget first, then ancestors).protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.The application's response to the user changing the selection: a click, a key, an assistive technology's select.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 callsrefresh(), which carries the selection and the focus cell to where their records are now.protected booleanonSyntheticAction(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).protected voidpaintChildren(Canvas canvas) Children pass; override to clip (e.g.voidrefresh()Re-reads the rows and re-lays out: call after the list's contents change.voidrevealRect(float x, float y, float rectWidth, float rectHeight) Scrolls the minimum amount so the rectangle becomes visible.introwCount()Names what makes a row the record it is, forrefresh()to follow the selection, the lead, the focus cell and the anchor across a change to the list:Order::idfor 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.rows()voidscrollBy(float dx, float dy) Scrolls by a delta in logical points: positivedytoward the last row, positivedxtoward the last column.Selects every row, inSelectionMode.MULTI; the lead row is kept, or becomes the first row.intint[]setBarLayout(ScrollGutters.Layout layout) Sets whether the bars float over the rows or reserve strips of their own (defaultScrollGutters.Layout.OVERLAY).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).setEmptyContent(@Nullable Widget<?> content) Showscontentin 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.setEmptyContent(@Nullable Widget<?> content, @Nullable Label message) Showscontentwhile there are no rows, assetEmptyContent(Widget)does, and describes the table bymessagewhile it shows, so a reader landing on it hears "table, 0 rows" and then the message.setEmptyMessage(@Nullable String message) Shows one fixed line while there are no rows; seesetEmptyMessage(I18nString).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.setFrozenColumns(int count) Keeps the first shown columns in place while the others scroll under them: the name beside a year of monthly figures.setLoading(boolean loading) Says the table is loading its rows, or has finished.setLoadingContent(@Nullable Widget<?> content) Replaces what shows while the table loads with no rows;nullbrings back the default, a moving bar over a muted "Loading…" in the table's language.setLoadingContent(@Nullable Widget<?> content, @Nullable Label message) Replaces what shows while the table loads with no rows, and describes the table bymessagewhile it shows.Hands the table the application's rows, held by reference and never copied.setScrollbarPolicy(ScrollBar.Policy policy) Sets when the scroll bars are shown (defaultScrollBar.Policy.AUTO).setSelectedRow(int modelIndex) Selects one row alone, moves the focus cell to it and scrolls it into view: a caller's write, so it announcesSELECTION/CODEand reaches no handler.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.Sets how many rows may be selected (defaultSelectionMode.SINGLE).setShowHeader(boolean show) Sets whether the header row is shown (default on).Orders the rows on a column, or restores the model's order withSortOrder.NONE.setStriped(boolean striped) Sets whether every second row is tinted (default on).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 aScrollViewor an unconstrained column — as a count of the step's seed rows (default 8); the header and the footer add their own strips.intviewToModel(int viewIndex) intHow many seed rows tall the rows' viewport prefers to be under an unbounded height.floatMethods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, controlSize, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, height, hitTest, hitTestChild, id, imageCursor, inheritanceHost, invalidate, invalidate, invalidateAccessible, isEnabled, isFocusable, isFocused, isRightToLeft, isSelfEnabled, isShowing, isVisible, isVisibleByItsOwnFlag, layoutBox, layoutDirection, locale, localToScene, localToSceneBounds, markNeedsContainedLayout, markNeedsLayout, markNeedsLayoutInPlace, maxHeight, maxWidth, measure, minHeight, minWidth, moveChild, moveChild, moveChild, neutralBase, notifyChange, notifyTextEdit, observeChanges, onAccessibilityChildAction, onAttached, onCharTyped, onPaint, onPaintOverlay, onPreedit, overlayPassesPointer, paintOutset, paintsDecoration, paintsFromBackdrop, paintWidget, parent, remove, remove, requestFocus, requestFocus, revealInView, revealInView, runHandler, scene, sceneNanos, sceneToLocal, self, setAccessibleDescribedBy, setAccessibleDescription, setAccessibleDescription, setAccessibleIgnored, setAccessibleLabelledBy, setAccessibleName, setAccessibleName, setAccessibleRole, setControlSize, setCursor, setEnabled, setFocusable, setId, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setMaxHeight, setMaxWidth, setMinHeight, setMinWidth, setPresentedElsewhere, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, tooltipSource, width, x, y
-
Constructor Details
-
Table
A table overcolumns, with no rows untilsetRows(java.util.List<T>).- Parameters:
columns- the columns, in reading order; at least one- Throws:
IllegalArgumentException- ifcolumnsis empty
-
-
Method Details
-
of
A table overcolumns.- Type Parameters:
T- the row type- Parameters:
columns- the columns, in reading order; at least one- Returns:
- the table
-
setRows
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, asrefresh()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 byColumn.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 byColumn.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 untilrefresh()- Returns:
- this table
-
rows
- Returns:
- the rows, as handed to
setRows(java.util.List<T>)
-
rowCount
public int rowCount()- Returns:
- how many rows the model holds
-
columns
- Returns:
- the columns, in reading order as they stand now; hidden ones included
-
moveColumn
Moves a column to another place amongcolumns(). 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:onColumnsChangeis not told.- Parameters:
column- one of this table's columnsindex- the place it takes amongcolumns(), from 0- Returns:
- this table
- Throws:
IllegalArgumentException- if the column is not this table'sIndexOutOfBoundsException- if there is no such place
-
setColumnReorderingAllowed
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
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- ifcountis negative
-
frozenColumns
public int frozenColumns()- Returns:
- how many leading shown columns are asked to stay in place
-
onColumnsChange
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 savescolumns()' order and widths. Never formoveColumn(limn.components.table.Column<T>, int)or a width set in code.- Parameters:
handler- the handler, ornullto clear the slot- Returns:
- this table
- Throws:
IllegalStateException- if a handler is already registered
-
widthOf
- Parameters:
column- one of this table's columns- Returns:
- the width the last layout gave it, weight included;
0while 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, byequals, 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 asSELECTION/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 asACTIVE/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. ThenCHILDREN/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 aftersetRows(java.util.List<T>). UI thread only. -
rowKey
Names what makes a row the record it is, forrefresh()to follow the selection, the lead, the focus cell and the anchor across a change to the list:Order::idfor 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 byequals; 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, ornullfor the record itself- Returns:
- this table
-
setSelectionMode
Sets how many rows may be selected (defaultSelectionMode.SINGLE). Narrowing the mode trims the selection to the lead row, or to nothing. UI thread only.- Parameters:
mode- the mode; nevernull- Returns:
- this table
-
selectionMode
- Returns:
- how many rows may be selected
-
setSelectedRow
Selects one row alone, moves the focus cell to it and scrolls it into view: a caller's write, so it announcesSELECTION/CODEand 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 rangeIllegalStateException- if the mode isSelectionMode.NONE
-
setSelectedRows
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. InSelectionMode.SINGLEonly one row may be named. AnnouncesSELECTION/CODEonce 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 rangeIllegalStateException- if the mode isSelectionMode.NONE, or SINGLE and more than one row is named
-
clearSelection
Drops the selection, announced asSELECTION/CODE; nothing happens when nothing is selected. The focus cell stays where it is. UI thread only.- Returns:
- this table
-
selectAll
Selects every row, inSelectionMode.MULTI; the lead row is kept, or becomes the first row. A caller's write, announced asSELECTION/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
-1when 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
The application's response to the user changing the selection: a click, a key, an assistive technology's select. Never forsetSelectedRow(int),setSelectedRows(int...),clearSelection(),selectAll()or asetRows(java.util.List<T>),refresh()orsetSelectionMode(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, ornullto clear the slot- Returns:
- this table
- Throws:
IllegalStateException- if a handler is already registered
-
onActivate
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 foractivate(), which is a caller's verb.- Parameters:
handler- the handler, ornullto clear the slot- Returns:
- this table
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
Description copied from class:WidgetRuns this widget's application handler for a change it has just announced. Called byWidget.runHandler(limn.scene.Change), and so byWidget.notifyChange(limn.scene.Change), only for aUSERorigin 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 boxselectedIndex(), a text fieldtext(). 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: aSearchFieldthat overrode this for its own aspect and did not chain would silently deleteTextField'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:
handleUserChangein classWidget<Table<T>>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
activate
public void activate()Announces that the cursor row — the focus cell's row — was opened, asINVOKED/CODE: a caller's verb, which reaches a watcher and notonActivate, 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
-1before 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
Orders the rows on a column, or restores the model's order withSortOrder.NONE. The application's list is untouched: the table keeps a permutation, and the selection, which is by model row, is unchanged. WhenonSortRequestis 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 noneorder- the order; nevernull- Returns:
- this table
- Throws:
IllegalArgumentException- if the column is not this table's or not sortable
-
sortColumn
- Returns:
- the column the rows are ordered on, or
nullin the model's order
-
sortOrder
- Returns:
- how the rows are ordered
-
onSortRequest
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 callsrefresh(), 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 neversetSort(limn.components.table.Column<T>, limn.components.table.SortOrder); one slot, as everyonXis:nullclears 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, ornullto 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
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
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
Sets when the scroll bars are shown (defaultScrollBar.Policy.AUTO).- Parameters:
policy- the policy- Returns:
- this table
-
scrollbarPolicy
- Returns:
- when the scroll bars are shown
-
setBarLayout
Sets whether the bars float over the rows or reserve strips of their own (defaultScrollGutters.Layout.OVERLAY).- Parameters:
layout- the layout- Returns:
- this table
-
barLayout
Whether the scroll bars overlay the rows or reserve strips beside them. -
setVisibleRows
Sets how many rows tall the rows' viewport prefers to be when the parent gives the table no height — a table inside aScrollViewor 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- ifrowsis 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: positivedytoward the last row, positivedxtoward the last column. UI thread only.- Parameters:
dx- how far along the columnsdy- how far along the rows
-
revealRect
public void revealRect(float x, float y, float rectWidth, float rectHeight) Description copied from interface:ScrollableScrolls 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'sx()/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:
revealRectin interfaceScrollable
-
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. 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
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved. -
setEmptyContent
Showscontentin 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, ornullfor none- Returns:
- this table
- Throws:
IllegalArgumentException- whencontentis already in another container (another widget showing it included), or holds this table
-
setEmptyContent
Showscontentwhile there are no rows, assetEmptyContent(Widget)does, and describes the table bymessagewhile it shows, so a reader landing on it hears "table, 0 rows" and then the message.- Parameters:
content- the content, ornullfor nonemessage- the label inside it that says why there are no rows, ornull- Returns:
- this table
- Throws:
IllegalArgumentException- whencontentis already in another container (another widget showing it included), or holds this table
-
setEmptyMessage
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, ornullfor no empty content- Returns:
- this table
-
setEmptyMessage
Shows one fixed line while there are no rows; seesetEmptyMessage(I18nString).- Parameters:
message- the words, ornullfor no empty content- Returns:
- this table
-
clearEmptyMessage
Takes the message shown with no rows away, assetEmptyMessage(null)would: that call does not compile, becausenullfits more than one of the overloads.- Returns:
- this table
-
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
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 bysetAccessibleName, or by a caption bound withLabel.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
Replaces what shows while the table loads with no rows;nullbrings back the default, a moving bar over a muted "Loading…" in the table's language.- Parameters:
content- the content, ornullfor the default- Returns:
- this table
- Throws:
IllegalArgumentException- whencontentis already in another container (another widget showing it included), or holds this table
-
setLoadingContent
Replaces what shows while the table loads with no rows, and describes the table bymessagewhile it shows.- Parameters:
content- the content, ornullfor the defaultmessage- the label inside it that says what is loading, ornull- Returns:
- this table
- Throws:
IllegalArgumentException- whencontentis 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:
onDetachedin classWidget<Table<T>>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child). -
clipsChildren
protected boolean clipsChildren()Description copied from class:WidgetWhetherWidget.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'sWidget.invalidate()damage to the visible region; a widget scrolled out of view damages nothing. Any override that clips inpaintChildrenshould also override this, and one whose clip is smaller than its box for some child should override the four clip accessors below too.- Overrides:
clipsChildrenin classWidget<Table<T>>
-
paintChildren
Description copied from class:WidgetChildren pass; override to clip (e.g. scroll views).Indexed rather than an enhanced
for, for the reasonScene'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:
paintChildrenin classWidget<Table<T>>
-
onHitTest
Description copied from class:WidgetWhat this widget answers for a point that reached it: one of its children's answers, itself, ornullto let the point through to what is beneath it. Called byWidget.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. -
cursor
- Overrides:
cursorin classWidget<Table<T>>- Returns:
- the mouse cursor this widget requests while hovered, or
nullto inherit from an ancestor (the scene falls back toCursor.DEFAULTat the root). Non-final so a component can override it with a state-dependent shape.
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<Table<T>>
-
onCanEdit
Description copied from class:WidgetWhether 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.
-
onEdit
Description copied from class:WidgetDoes an editing command, as the user's own act. Called on the first widget, from the one with the keyboard upward, whoseWidget.onCanEdit(limn.scene.EditCommand)answeredtrue. -
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<Table<T>>
-
onDragStart
Description copied from class:WidgetAsked 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 firstDraganswered starts the drag; the press is then over for the widget that had it. Default:null, which leaves the gesture to theDRAGevents it has always been.- Overrides:
onDragStartin classWidget<Table<T>>- Parameters:
event- where the press began, and whether the keyboard asked- Returns:
- what to drag, or null
-
onDragOver
Description copied from class:WidgetA drag is over this widget, or has moved on it. A widget that would take what is offered callsevent.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 holdingDataFormat.FILES. Whatever this draws, accepting or not,Widget.onDragExit(limn.scene.event.DragEvent)takes away. Default: says nothing.- Overrides:
onDragOverin classWidget<Table<T>>- Parameters:
event- what is offered and where
-
onDragExit
Description copied from class:WidgetThe drag this widget was last asked about inWidget.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 afterWidget.onDrop(limn.scene.event.DropEvent): the widget handed the drop hears no exit. Default: nothing.- Overrides:
onDragExitin classWidget<Table<T>>- Parameters:
event- the drag as it leaves
-
onDrop
Description copied from class:WidgetWhat was dragged is dropped here: on the widget that accepted it last. Read what is wanted fromevent.offer()and carry outevent.action(); whatever marked the place goes now, since noWidget.onDragExit(limn.scene.event.DragEvent)follows. Default: nothing. -
onFocusGained
protected void onFocusGained()Description copied from class:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<Table<T>>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<Table<T>>
-
clipX
Description copied from class:WidgetThe left edge, in this widget's own coordinates, of the rectangleWidget.clipsChildren()clipschildto.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 whileclipsChildren()is true, and it must not allocate, because the showing test runs for every node of every accessible walk. -
clipY
Description copied from class:WidgetThe top edge of the rectangle this widget clipschildto; seeWidget.clipX(Widget). -
clipWidth
Description copied from class:WidgetThe width of the rectangle this widget clipschildto; seeWidget.clipX(Widget). -
clipHeight
Description copied from class:WidgetThe height of the rectangle this widget clipschildto; seeWidget.clipX(Widget).- Overrides:
clipHeightin classWidget<Table<T>>- Parameters:
child- the direct child the clipped descendant is under- Returns:
- the clip rectangle's height
-
onAccessibility
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 ownI18nString.- Overrides:
onAccessibilityin classWidget<Table<T>>- Parameters:
a- the node being described
-
onAccessibilityChildIdentity
A widget cell hangs under the syntheticROWof 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:
onAccessibilityChildIdentityin classWidget<Table<T>>- Parameters:
child- the child about to be describeda- the builder, open only for the two identity calls
-
onAccessibilityChild
Description copied from class:WidgetAdds 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'sSELECTon a row — is published on the child's node and routed toWidget.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 forFOCUSorSCROLL_INTO_VIEWon a focusable child, which the walk performs on its own. A verb written here withAccessibility.action(limn.accessibility.Accessible.Action)would be dispatched to the child's ownWidget.onAccessibilityAction(limn.accessibility.Accessible.Action, limn.accessibility.Accessible.Argument), which is why a child hook writes facts and delegates verbs.- Overrides:
onAccessibilityChildin classWidget<Table<T>>- Parameters:
child- the child being describeda- the child's node
-
onAccessibilityAction
Description copied from class:WidgetPerforms 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
falsehere is honest bookkeeping and nothing more — a refusedPRESSraises noINVOKED— 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 answertruefor a verb it did not publish is a defect the gallery ratchet inlimn-demoreports; a widget that accepts a synonym publishes it.- Overrides:
onAccessibilityActionin classWidget<Table<T>>- Parameters:
action- what was asked; one of the seventeen, including the four that carry an argumentarg- the argument, orAccessible.Argument.NONE- Returns:
- whether this widget did it.
falseis the honest answer for a verb it does not offer.
-
onSyntheticAction
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 acceptsFOCUSalone — and each goes through the seam the matching gesture takes atUSER:SELECTis the click,ADD_TO_SELECTIONandDESELECTthe command-click's toggle without its cursor move (onlySELECTandFOCUSmove the cursor),FOCUSa cursor move that selects nothing. A row is named by the identity of the record the snapshot published it for: a verb sent before arefresh()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:
onSyntheticActionin classWidget<Table<T>>- Parameters:
key- the key this widget gave that child when it declared itaction- what was askedarg- the argument, orAccessible.Argument.NONE- Returns:
- whether this widget did it
-